├── deps ├── LuaJIT-2.0.4.tar.gz └── openssl-1.1.0d.tar.gz ├── src ├── aprintf.h ├── units.h ├── ssl.h ├── config.h ├── aprintf.c ├── net.h ├── stats.h ├── net.c ├── script.h ├── wrk.h ├── main.h ├── wrk.lua ├── ssl.c ├── units.c ├── stats.c ├── atomicvar.h ├── zmalloc.h ├── ae_select.c ├── ae_kqueue.c ├── ae.h ├── ae_epoll.c ├── ae_evport.c ├── zmalloc.c ├── ae.c ├── script.c ├── http_parser.h ├── ans_module.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.md ├── Makefile ├── SCRIPTING ├── NOTICE └── LICENSE /deps/LuaJIT-2.0.4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansyun/dpdk-httpperf/HEAD/deps/LuaJIT-2.0.4.tar.gz -------------------------------------------------------------------------------- /deps/openssl-1.1.0d.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansyun/dpdk-httpperf/HEAD/deps/openssl-1.1.0d.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 | master 2 | 3 | * Require OpenSSL 1.1.0+ 4 | 5 | wrk 4.0.2 6 | 7 | * Send hostname using TLS SNI. 8 | * Add optional WITH_OPENSSL and WITH_LUAJIT to use system libs. 9 | * Bundle OpenSSL 1.0.2. 10 | * delay() can return milliseconds to delay sending next request. 11 | 12 | wrk 4.0.0 13 | 14 | * The wrk global variable is the only global defined by default. 15 | * wrk.init() calls the global init(), remove calls to wrk.init(). 16 | * Add wrk.lookup(host, port) and wrk.connect(addr) functions. 17 | * Add setup phase that calls the global setup() for each thread. 18 | * Allow assignment to thread.addr to specify the server address. 19 | * Add thread:set(name, value), thread:get(name), and thread:stop(). 20 | * Record latency for every request instead of random samples. 21 | * Latency and requests in done() are now callable, not indexable. 22 | * Only record timeouts when a response is actually received. 23 | * Remove calibration phase and record rate at fixed interval. 24 | * Improve correction of coordinated omission. 25 | -------------------------------------------------------------------------------- /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/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 | 19 | ssize_t r = read(c->fd, c->buf, sizeof(c->buf)); 20 | if (r == -1) { 21 | switch (errno) { 22 | case EAGAIN: return RETRY; 23 | default: return ERROR; 24 | } 25 | } 26 | else if(r == 0) 27 | { 28 | return ERROR; 29 | } 30 | 31 | *n = (size_t) r; 32 | return OK; 33 | 34 | } 35 | 36 | status sock_write(connection *c, char *buf, size_t len, size_t *n) { 37 | ssize_t r; 38 | if ((r = write(c->fd, buf, len)) == -1) { 39 | switch (errno) { 40 | case EAGAIN: return RETRY; 41 | default: return ERROR; 42 | } 43 | } 44 | *n = (size_t) r; 45 | return OK; 46 | } 47 | 48 | size_t sock_readable(connection *c) { 49 | int n, rc; 50 | rc = ioctl(c->fd, FIONREAD, &n); 51 | return rc == -1 ? 0 : n; 52 | } 53 | -------------------------------------------------------------------------------- /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 | uint32_t ip_start; 32 | uint32_t ip_end; 33 | uint64_t connections; 34 | uint64_t complete; 35 | uint64_t requests; 36 | uint64_t bytes; 37 | uint64_t start; 38 | lua_State *L; 39 | errors errors; 40 | struct connection *cs; 41 | } thread; 42 | 43 | typedef struct { 44 | char *buffer; 45 | size_t length; 46 | char *cursor; 47 | } buffer; 48 | 49 | typedef struct connection { 50 | thread *thread; 51 | http_parser parser; 52 | enum { 53 | FIELD, VALUE 54 | } state; 55 | int fd; 56 | uint32_t local_ip; 57 | SSL *ssl; 58 | bool delayed; 59 | uint64_t start; 60 | char *request; 61 | size_t length; 62 | size_t written; 63 | uint64_t pending; 64 | buffer headers; 65 | buffer body; 66 | char buf[RECVBUF]; 67 | } connection; 68 | 69 | #endif /* WRK_H */ 70 | -------------------------------------------------------------------------------- /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/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 | SSL_CTX *ssl_init() { 12 | SSL_CTX *ctx = NULL; 13 | 14 | SSL_load_error_strings(); 15 | SSL_library_init(); 16 | OpenSSL_add_all_algorithms(); 17 | 18 | if ((ctx = SSL_CTX_new(SSLv23_client_method()))) { 19 | SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); 20 | SSL_CTX_set_verify_depth(ctx, 0); 21 | SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); 22 | SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_CLIENT); 23 | } 24 | 25 | return ctx; 26 | } 27 | 28 | status ssl_connect(connection *c, char *host) { 29 | int r; 30 | SSL_set_fd(c->ssl, c->fd); 31 | SSL_set_tlsext_host_name(c->ssl, host); 32 | if ((r = SSL_connect(c->ssl)) != 1) { 33 | switch (SSL_get_error(c->ssl, r)) { 34 | case SSL_ERROR_WANT_READ: return RETRY; 35 | case SSL_ERROR_WANT_WRITE: return RETRY; 36 | default: return ERROR; 37 | } 38 | } 39 | return OK; 40 | } 41 | 42 | status ssl_close(connection *c) { 43 | SSL_shutdown(c->ssl); 44 | SSL_clear(c->ssl); 45 | return OK; 46 | } 47 | 48 | status ssl_read(connection *c, size_t *n) { 49 | int r; 50 | if ((r = SSL_read(c->ssl, c->buf, sizeof(c->buf))) <= 0) { 51 | switch (SSL_get_error(c->ssl, r)) { 52 | case SSL_ERROR_WANT_READ: return RETRY; 53 | case SSL_ERROR_WANT_WRITE: return RETRY; 54 | default: return ERROR; 55 | } 56 | } 57 | *n = (size_t) r; 58 | return OK; 59 | } 60 | 61 | status ssl_write(connection *c, char *buf, size_t len, size_t *n) { 62 | int r; 63 | if ((r = SSL_write(c->ssl, buf, len)) <= 0) { 64 | switch (SSL_get_error(c->ssl, r)) { 65 | case SSL_ERROR_WANT_READ: return RETRY; 66 | case SSL_ERROR_WANT_WRITE: return RETRY; 67 | default: return ERROR; 68 | } 69 | } 70 | *n = (size_t) r; 71 | return OK; 72 | } 73 | 74 | size_t ssl_readable(connection *c) { 75 | return SSL_pending(c->ssl); 76 | } 77 | -------------------------------------------------------------------------------- /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.md: -------------------------------------------------------------------------------- 1 | #### dpdk-httpperf 2 | -------------- 3 | Fork from official wrk(https://github.com/wg/wrk), and run on the dpdk user space TCP/IP stack(ANS"Acceleted Network Stack"). 4 | 5 | #### build and install 6 | -------------- 7 | * Download latest dpdk version from [dpdk website](http://dpdk.org/), and build dpdk 8 | ``` 9 | $ make config T=x86_64-native-linuxapp-gcc 10 | $ make install T=x86_64-native-linuxapp-gcc 11 | $ export RTE_SDK=/home/mytest/dpdk 12 | $ export RTE_TARGET=x86_64-native-linuxapp-gcc 13 | ``` 14 | * Download ANS following the [ANS wiki](https://github.com/opendp/dpdk-ans/wiki/Compile-APP-with-ans), build ans and startup ans 15 | ``` 16 | $ git clone https://github.com/ansyun/dpdk-ans.git 17 | $ export RTE_ANS=/home/mytest/dpdk-ans 18 | $ ./install_deps.sh 19 | $ cd ans 20 | $ make 21 | $ sudo ./build/ans -c 0x2 -n 1 -- -p 0x1 --config="(0,0,1)" 22 | EAL: Detected lcore 0 as core 0 on socket 0 23 | EAL: Detected lcore 1 as core 1 on socket 0 24 | EAL: Support maximum 128 logical core(s) by configuration. 25 | EAL: Detected 2 lcore(s) 26 | EAL: VFIO modules not all loaded, skip VFIO support... 27 | EAL: Setting up physically contiguous memory... 28 | EAL: Ask a virtual area of 0x400000 bytes 29 | EAL: Virtual area found at 0x7fdf90c00000 (size = 0x400000) 30 | EAL: Ask a virtual area of 0x15400000 bytes 31 | ``` 32 | * Download dpdk-httpperf, build dpdk-httpperf and startup dpdk-httpperf 33 | ``` 34 | $ git clone https://github.com/ansyun/dpdk-httpperf.git 35 | $ make 36 | root@ubuntu:~/src/dpdk-httpperf# ./dpdk-httpperf -t12 -c5000 -d30s http://10.0.0.10:80/index.html 37 | start init anssock 38 | EAL: Detected lcore 0 as core 0 on socket 0 39 | EAL: Detected lcore 1 as core 1 on socket 0 40 | EAL: Detected lcore 2 as core 2 on socket 0 41 | EAL: Detected lcore 3 as core 3 on socket 0 42 | EAL: Detected lcore 4 as core 4 on socket 0 43 | EAL: Detected lcore 5 as core 5 on socket 0 44 | EAL: Detected lcore 6 as core 6 on socket 0 45 | EAL: Detected lcore 7 as core 7 on socket 0 46 | EAL: Support maximum 128 logical core(s) by configuration. 47 | EAL: Detected 8 lcore(s) 48 | EAL: Setting up physically contiguous memory... 49 | EAL: WARNING: Address Space Layout Randomization (ASLR) is enabled in the kernel. 50 | EAL: This may cause issues with mapping memory into secondary processes 51 | EAL: Analysing 4 files 52 | EAL: Mapped segment 0 of size 0x100000000 53 | EAL: TSC frequency is ~1698035 KHz 54 | EAL: Master lcore 0 is ready (tid=988e0900;cpuset=[0]) 55 | USER8: LCORE[-1] anssock any lcore id 0xffffffff 56 | USER8: LCORE[0] anssock app id: 14265 57 | USER8: LCORE[0] anssock app name: dpdk-httpperf 58 | USER8: LCORE[0] anssock app lcoreId: 0 59 | USER8: LCORE[0] mp ops number 4, mp ops index: 0 60 | Running 30s test @ http://10.0.0.10:80/index.html 61 | 12 threads and 5000 connections 62 | Thread Stats Avg Stdev Max +/- Stdev 63 | Latency 61.63ms 163.62ms 1.96s 96.67% 64 | Req/Sec 227.53 790.62 3.85k 91.84% 65 | 2794 requests in 0.93m, 2.26MB read 66 | Socket errors: connect 0, read 4866, write 0, timeout 782 67 | Requests/sec: 50.22 68 | Transfer/sec: 41.69KB 69 | ``` 70 | 71 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | ifeq ($(RTE_SDK),) 21 | $(error "Please define RTE_SDK environment variable") 22 | endif 23 | 24 | ifeq ($(RTE_ANS),) 25 | $(error "Please define RTE_ANS environment variable") 26 | endif 27 | 28 | ifeq ($(RTE_TARGET),) 29 | $(error "Please define RTE_TARGET environment variable") 30 | endif 31 | 32 | CFLAGS += -I$(RTE_ANS)/librte_ans/include \ 33 | -I$(RTE_ANS)/librte_anssock/include 34 | 35 | LIBS += $(RTE_ANS)/librte_anssock/librte_anssock.a \ 36 | -L$(RTE_SDK)/$(RTE_TARGET)/lib \ 37 | -Wl,--whole-archive -Wl,-lrte_mbuf -Wl,-lrte_mempool_ring -Wl,-lrte_mempool -Wl,-lrte_ring -Wl,-lrte_eal -Wl,-lrte_kvargs -Wl,--no-whole-archive -Wl,-export-dynamic -lnuma\ 38 | 39 | SRC := wrk.c net.c ssl.c aprintf.c stats.c script.c units.c \ 40 | ae.c zmalloc.c http_parser.c ans_module.c 41 | BIN := dpdk-httpperf 42 | VER ?= $(shell git describe --tags --always --dirty) 43 | 44 | ODIR := obj 45 | OBJ := $(patsubst %.c,$(ODIR)/%.o,$(SRC)) $(ODIR)/bytecode.o $(ODIR)/version.o 46 | LIBS := -lluajit-5.1 $(LIBS) 47 | 48 | DEPS := 49 | CFLAGS += -I$(ODIR)/include 50 | LDFLAGS += -L$(ODIR)/lib 51 | 52 | ifneq ($(WITH_LUAJIT),) 53 | CFLAGS += -I$(WITH_LUAJIT)/include 54 | LDFLAGS += -L$(WITH_LUAJIT)/lib 55 | else 56 | DEPS += $(ODIR)/lib/libluajit-5.1.a 57 | endif 58 | 59 | ifneq ($(WITH_OPENSSL),) 60 | CFLAGS += -I$(WITH_OPENSSL)/include 61 | LDFLAGS += -L$(WITH_OPENSSL)/lib 62 | else 63 | DEPS += $(ODIR)/lib/libssl.a 64 | endif 65 | 66 | all: $(BIN) 67 | 68 | clean: 69 | $(RM) -rf $(BIN) obj/* 70 | 71 | $(BIN): $(OBJ) 72 | @echo LINK $(BIN) 73 | @$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) 74 | 75 | $(OBJ): config.h Makefile $(DEPS) | $(ODIR) 76 | 77 | $(ODIR): 78 | @mkdir -p $@ 79 | 80 | $(ODIR)/bytecode.o: src/wrk.lua 81 | @echo LUAJIT $< 82 | @$(SHELL) -c 'PATH=obj/bin:$(PATH) luajit -b $(CURDIR)/$< $(CURDIR)/$@' 83 | 84 | $(ODIR)/version.o: 85 | @echo 'const char *VERSION="$(VER)";' | $(CC) -xc -c -o $@ - 86 | 87 | $(ODIR)/%.o : %.c 88 | @echo CC $< 89 | @$(CC) $(CFLAGS) -c -o $@ $< 90 | 91 | # Dependencies 92 | 93 | LUAJIT := $(notdir $(patsubst %.tar.gz,%,$(wildcard deps/LuaJIT*.tar.gz))) 94 | OPENSSL := $(notdir $(patsubst %.tar.gz,%,$(wildcard deps/openssl*.tar.gz))) 95 | 96 | OPENSSL_OPTS = no-shared no-psk no-srp no-dtls no-idea --prefix=$(abspath $(ODIR)) 97 | 98 | $(ODIR)/$(LUAJIT): deps/$(LUAJIT).tar.gz | $(ODIR) 99 | @tar -C $(ODIR) -xf $< 100 | 101 | $(ODIR)/$(OPENSSL): deps/$(OPENSSL).tar.gz | $(ODIR) 102 | @tar -C $(ODIR) -xf $< 103 | 104 | $(ODIR)/lib/libluajit-5.1.a: $(ODIR)/$(LUAJIT) 105 | @echo Building LuaJIT... 106 | @$(MAKE) -C $< PREFIX=$(abspath $(ODIR)) BUILDMODE=static install 107 | 108 | $(ODIR)/lib/libssl.a: $(ODIR)/$(OPENSSL) 109 | @echo Building OpenSSL... 110 | ifeq ($(TARGET), darwin) 111 | @$(SHELL) -c "cd $< && ./Configure $(OPENSSL_OPTS) darwin64-x86_64-cc" 112 | else 113 | @$(SHELL) -c "cd $< && ./config $(OPENSSL_OPTS)" 114 | endif 115 | @$(MAKE) -C $< depend 116 | @$(MAKE) -C $< 117 | @$(MAKE) -C $< install_sw 118 | @touch $@ 119 | 120 | # ------------ 121 | 122 | .PHONY: all clean 123 | .PHONY: $(ODIR)/version.o 124 | 125 | .SUFFIXES: 126 | .SUFFIXES: .c .o .lua 127 | 128 | vpath %.c src 129 | vpath %.h src 130 | vpath %.lua scripts 131 | -------------------------------------------------------------------------------- /src/atomicvar.h: -------------------------------------------------------------------------------- 1 | /* This file implements atomic counters using __atomic or __sync macros if 2 | * available, otherwise synchronizing different threads using a mutex. 3 | * 4 | * The exported interaface is composed of three macros: 5 | * 6 | * atomicIncr(var,count,mutex) -- Increment the atomic counter 7 | * atomicDecr(var,count,mutex) -- Decrement the atomic counter 8 | * atomicGet(var,dstvar,mutex) -- Fetch the atomic counter value 9 | * 10 | * If atomic primitives are availble (tested in config.h) the mutex 11 | * is not used. 12 | * 13 | * Never use return value from the macros. To update and get use instead: 14 | * 15 | * atomicIncr(mycounter,...); 16 | * atomicGet(mycounter,newvalue); 17 | * doSomethingWith(newvalue); 18 | * 19 | * ---------------------------------------------------------------------------- 20 | * 21 | * Copyright (c) 2015, 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 met: 26 | * 27 | * * Redistributions of source code must retain the above copyright notice, 28 | * this list of conditions and the following disclaimer. 29 | * * Redistributions in binary form must reproduce the above copyright 30 | * notice, this list of conditions and the following disclaimer in the 31 | * documentation and/or other materials provided with the distribution. 32 | * * Neither the name of Redis nor the names of its contributors may be used 33 | * to endorse or promote products derived from this software without 34 | * specific prior written permission. 35 | * 36 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 37 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 38 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 39 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 40 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 41 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 42 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 43 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 44 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 45 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 46 | * POSSIBILITY OF SUCH DAMAGE. 47 | */ 48 | 49 | #include 50 | 51 | #ifndef __ATOMIC_VAR_H 52 | #define __ATOMIC_VAR_H 53 | 54 | #if defined(__ATOMIC_RELAXED) && (!defined(__clang__) || !defined(__APPLE__) || __apple_build_version__ > 4210057) 55 | /* Implementation using __atomic macros. */ 56 | 57 | #define atomicIncr(var,count,mutex) __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED) 58 | #define atomicDecr(var,count,mutex) __atomic_sub_fetch(&var,(count),__ATOMIC_RELAXED) 59 | #define atomicGet(var,dstvar,mutex) do { \ 60 | dstvar = __atomic_load_n(&var,__ATOMIC_RELAXED); \ 61 | } while(0) 62 | 63 | #elif defined(HAVE_ATOMIC) 64 | /* Implementation using __sync macros. */ 65 | 66 | #define atomicIncr(var,count,mutex) __sync_add_and_fetch(&var,(count)) 67 | #define atomicDecr(var,count,mutex) __sync_sub_and_fetch(&var,(count)) 68 | #define atomicGet(var,dstvar,mutex) do { \ 69 | dstvar = __sync_sub_and_fetch(&var,0); \ 70 | } while(0) 71 | 72 | #else 73 | /* Implementation using pthread mutex. */ 74 | 75 | #define atomicIncr(var,count,mutex) do { \ 76 | pthread_mutex_lock(&mutex); \ 77 | var += (count); \ 78 | pthread_mutex_unlock(&mutex); \ 79 | } while(0) 80 | 81 | #define atomicDecr(var,count,mutex) do { \ 82 | pthread_mutex_lock(&mutex); \ 83 | var -= (count); \ 84 | pthread_mutex_unlock(&mutex); \ 85 | } while(0) 86 | 87 | #define atomicGet(var,dstvar,mutex) do { \ 88 | pthread_mutex_lock(&mutex); \ 89 | dstvar = var; \ 90 | pthread_mutex_unlock(&mutex); \ 91 | } while(0) 92 | #endif 93 | 94 | #endif /* __ATOMIC_VAR_H */ 95 | -------------------------------------------------------------------------------- /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) || (TC_VERSION_MAJOR > 1) 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 | #include 51 | #if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2) 52 | #define HAVE_MALLOC_SIZE 1 53 | #define zmalloc_size(p) je_malloc_usable_size(p) 54 | #else 55 | #error "Newer version of jemalloc required" 56 | #endif 57 | 58 | #elif defined(__APPLE__) 59 | #include 60 | #define HAVE_MALLOC_SIZE 1 61 | #define zmalloc_size(p) malloc_size(p) 62 | #endif 63 | 64 | #ifndef ZMALLOC_LIB 65 | #define ZMALLOC_LIB "libc" 66 | #endif 67 | 68 | /* We can enable the Redis defrag capabilities only if we are using Jemalloc 69 | * and the version used is our special version modified for Redis having 70 | * the ability to return per-allocation fragmentation hints. */ 71 | #if defined(USE_JEMALLOC) && defined(JEMALLOC_FRAG_HINT) 72 | #define HAVE_DEFRAG 73 | #endif 74 | 75 | void *zmalloc(size_t size); 76 | void *zcalloc(size_t size); 77 | void *zrealloc(void *ptr, size_t size); 78 | void zfree(void *ptr); 79 | char *zstrdup(const char *s); 80 | size_t zmalloc_used_memory(void); 81 | void zmalloc_enable_thread_safeness(void); 82 | void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); 83 | float zmalloc_get_fragmentation_ratio(size_t rss); 84 | size_t zmalloc_get_rss(void); 85 | size_t zmalloc_get_private_dirty(long pid); 86 | size_t zmalloc_get_smap_bytes_by_field(char *field, long pid); 87 | size_t zmalloc_get_memory_size(void); 88 | void zlibc_free(void *ptr); 89 | 90 | #ifdef HAVE_DEFRAG 91 | void zfree_no_tcache(void *ptr); 92 | void *zmalloc_no_tcache(size_t size); 93 | #endif 94 | 95 | #ifndef HAVE_MALLOC_SIZE 96 | size_t zmalloc_size(void *ptr); 97 | #endif 98 | 99 | #endif /* __ZMALLOC_H */ 100 | -------------------------------------------------------------------------------- /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 | #include 34 | 35 | typedef struct aeApiState { 36 | fd_set rfds, wfds; 37 | /* We need to have a copy of the fd sets as it's not safe to reuse 38 | * FD sets after select(). */ 39 | fd_set _rfds, _wfds; 40 | } aeApiState; 41 | 42 | static int aeApiCreate(aeEventLoop *eventLoop) { 43 | aeApiState *state = zmalloc(sizeof(aeApiState)); 44 | 45 | if (!state) return -1; 46 | FD_ZERO(&state->rfds); 47 | FD_ZERO(&state->wfds); 48 | eventLoop->apidata = state; 49 | return 0; 50 | } 51 | 52 | static int aeApiResize(aeEventLoop *eventLoop, int setsize) { 53 | /* Just ensure we have enough room in the fd_set type. */ 54 | if (setsize >= FD_SETSIZE) return -1; 55 | return 0; 56 | } 57 | 58 | static void aeApiFree(aeEventLoop *eventLoop) { 59 | zfree(eventLoop->apidata); 60 | } 61 | 62 | static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 63 | aeApiState *state = eventLoop->apidata; 64 | 65 | if (mask & AE_READABLE) FD_SET(fd,&state->rfds); 66 | if (mask & AE_WRITABLE) FD_SET(fd,&state->wfds); 67 | return 0; 68 | } 69 | 70 | static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { 71 | aeApiState *state = eventLoop->apidata; 72 | 73 | if (mask & AE_READABLE) FD_CLR(fd,&state->rfds); 74 | if (mask & AE_WRITABLE) FD_CLR(fd,&state->wfds); 75 | } 76 | 77 | static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { 78 | aeApiState *state = eventLoop->apidata; 79 | int retval, j, numevents = 0; 80 | 81 | memcpy(&state->_rfds,&state->rfds,sizeof(fd_set)); 82 | memcpy(&state->_wfds,&state->wfds,sizeof(fd_set)); 83 | 84 | retval = select(eventLoop->maxfd+1, 85 | &state->_rfds,&state->_wfds,NULL,tvp); 86 | if (retval > 0) { 87 | for (j = 0; j <= eventLoop->maxfd; j++) { 88 | int mask = 0; 89 | aeFileEvent *fe = &eventLoop->events[j]; 90 | 91 | if (fe->mask == AE_NONE) continue; 92 | if (fe->mask & AE_READABLE && FD_ISSET(j,&state->_rfds)) 93 | mask |= AE_READABLE; 94 | if (fe->mask & AE_WRITABLE && FD_ISSET(j,&state->_wfds)) 95 | mask |= AE_WRITABLE; 96 | eventLoop->fired[numevents].fd = j; 97 | eventLoop->fired[numevents].mask = mask; 98 | numevents++; 99 | } 100 | } 101 | return numevents; 102 | } 103 | 104 | static char *aeApiName(void) { 105 | return "select"; 106 | } 107 | -------------------------------------------------------------------------------- /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 | return 0; 58 | } 59 | 60 | static int aeApiResize(aeEventLoop *eventLoop, int setsize) { 61 | aeApiState *state = eventLoop->apidata; 62 | 63 | state->events = zrealloc(state->events, sizeof(struct kevent)*setsize); 64 | return 0; 65 | } 66 | 67 | static void aeApiFree(aeEventLoop *eventLoop) { 68 | aeApiState *state = eventLoop->apidata; 69 | 70 | close(state->kqfd); 71 | zfree(state->events); 72 | zfree(state); 73 | } 74 | 75 | static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 76 | aeApiState *state = eventLoop->apidata; 77 | struct kevent ke; 78 | 79 | if (mask & AE_READABLE) { 80 | EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, NULL); 81 | if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1; 82 | } 83 | if (mask & AE_WRITABLE) { 84 | EV_SET(&ke, fd, EVFILT_WRITE, EV_ADD, 0, 0, NULL); 85 | if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1; 86 | } 87 | return 0; 88 | } 89 | 90 | static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { 91 | aeApiState *state = eventLoop->apidata; 92 | struct kevent ke; 93 | 94 | if (mask & AE_READABLE) { 95 | EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); 96 | kevent(state->kqfd, &ke, 1, NULL, 0, NULL); 97 | } 98 | if (mask & AE_WRITABLE) { 99 | EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); 100 | kevent(state->kqfd, &ke, 1, NULL, 0, NULL); 101 | } 102 | } 103 | 104 | static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { 105 | aeApiState *state = eventLoop->apidata; 106 | int retval, numevents = 0; 107 | 108 | if (tvp != NULL) { 109 | struct timespec timeout; 110 | timeout.tv_sec = tvp->tv_sec; 111 | timeout.tv_nsec = tvp->tv_usec * 1000; 112 | retval = kevent(state->kqfd, NULL, 0, state->events, eventLoop->setsize, 113 | &timeout); 114 | } else { 115 | retval = kevent(state->kqfd, NULL, 0, state->events, eventLoop->setsize, 116 | NULL); 117 | } 118 | 119 | if (retval > 0) { 120 | int j; 121 | 122 | numevents = retval; 123 | for(j = 0; j < numevents; j++) { 124 | int mask = 0; 125 | struct kevent *e = state->events+j; 126 | 127 | if (e->filter == EVFILT_READ) mask |= AE_READABLE; 128 | if (e->filter == EVFILT_WRITE) mask |= AE_WRITABLE; 129 | eventLoop->fired[j].fd = e->ident; 130 | eventLoop->fired[j].mask = mask; 131 | } 132 | } 133 | return numevents; 134 | } 135 | 136 | static char *aeApiName(void) { 137 | return "kqueue"; 138 | } 139 | -------------------------------------------------------------------------------- /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 | #include 37 | 38 | #define AE_OK 0 39 | #define AE_ERR -1 40 | 41 | #define AE_NONE 0 42 | #define AE_READABLE 1 43 | #define AE_WRITABLE 2 44 | 45 | #define AE_FILE_EVENTS 1 46 | #define AE_TIME_EVENTS 2 47 | #define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS) 48 | #define AE_DONT_WAIT 4 49 | 50 | #define AE_NOMORE -1 51 | #define AE_DELETED_EVENT_ID -1 52 | 53 | /* Macros */ 54 | #define AE_NOTUSED(V) ((void) V) 55 | 56 | struct aeEventLoop; 57 | 58 | /* Types and data structures */ 59 | typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask); 60 | typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData); 61 | typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData); 62 | typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop); 63 | 64 | /* File event structure */ 65 | typedef struct aeFileEvent { 66 | int mask; /* one of AE_(READABLE|WRITABLE) */ 67 | aeFileProc *rfileProc; 68 | aeFileProc *wfileProc; 69 | void *clientData; 70 | } aeFileEvent; 71 | 72 | /* Time event structure */ 73 | typedef struct aeTimeEvent { 74 | long long id; /* time event identifier. */ 75 | long when_sec; /* seconds */ 76 | long when_ms; /* milliseconds */ 77 | aeTimeProc *timeProc; 78 | aeEventFinalizerProc *finalizerProc; 79 | void *clientData; 80 | struct aeTimeEvent *next; 81 | } aeTimeEvent; 82 | 83 | /* A fired event */ 84 | typedef struct aeFiredEvent { 85 | int fd; 86 | int mask; 87 | } aeFiredEvent; 88 | 89 | /* State of an event based program */ 90 | typedef struct aeEventLoop { 91 | int maxfd; /* highest file descriptor currently registered */ 92 | int setsize; /* max number of file descriptors tracked */ 93 | long long timeEventNextId; 94 | time_t lastTime; /* Used to detect system clock skew */ 95 | aeFileEvent *events; /* Registered events */ 96 | aeFiredEvent *fired; /* Fired events */ 97 | aeTimeEvent *timeEventHead; 98 | int stop; 99 | void *apidata; /* This is used for polling API specific data */ 100 | aeBeforeSleepProc *beforesleep; 101 | } aeEventLoop; 102 | 103 | /* Prototypes */ 104 | aeEventLoop *aeCreateEventLoop(int setsize); 105 | void aeDeleteEventLoop(aeEventLoop *eventLoop); 106 | void aeStop(aeEventLoop *eventLoop); 107 | int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, 108 | aeFileProc *proc, void *clientData); 109 | void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask); 110 | int aeGetFileEvents(aeEventLoop *eventLoop, int fd); 111 | long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, 112 | aeTimeProc *proc, void *clientData, 113 | aeEventFinalizerProc *finalizerProc); 114 | int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id); 115 | int aeProcessEvents(aeEventLoop *eventLoop, int flags); 116 | int aeWait(int fd, int mask, long long milliseconds); 117 | void aeMain(aeEventLoop *eventLoop); 118 | char *aeGetApiName(void); 119 | void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep); 120 | int aeGetSetSize(aeEventLoop *eventLoop); 121 | int aeResizeSetSize(aeEventLoop *eventLoop, int setsize); 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /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 a 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 int aeApiResize(aeEventLoop *eventLoop, int setsize) { 59 | aeApiState *state = eventLoop->apidata; 60 | 61 | state->events = zrealloc(state->events, sizeof(struct epoll_event)*setsize); 62 | return 0; 63 | } 64 | 65 | static void aeApiFree(aeEventLoop *eventLoop) { 66 | aeApiState *state = eventLoop->apidata; 67 | 68 | close(state->epfd); 69 | zfree(state->events); 70 | zfree(state); 71 | } 72 | 73 | static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 74 | aeApiState *state = eventLoop->apidata; 75 | struct epoll_event ee = {0}; /* avoid valgrind warning */ 76 | /* If the fd was already monitored for some event, we need a MOD 77 | * operation. Otherwise we need an ADD operation. */ 78 | int op = eventLoop->events[fd].mask == AE_NONE ? 79 | EPOLL_CTL_ADD : EPOLL_CTL_MOD; 80 | 81 | ee.events = 0; 82 | mask |= eventLoop->events[fd].mask; /* Merge old events */ 83 | if (mask & AE_READABLE) ee.events |= EPOLLIN; 84 | if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; 85 | ee.data.fd = fd; 86 | if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1; 87 | return 0; 88 | } 89 | 90 | static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) { 91 | aeApiState *state = eventLoop->apidata; 92 | struct epoll_event ee = {0}; /* avoid valgrind warning */ 93 | int mask = eventLoop->events[fd].mask & (~delmask); 94 | 95 | ee.events = 0; 96 | if (mask & AE_READABLE) ee.events |= EPOLLIN; 97 | if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; 98 | ee.data.fd = fd; 99 | if (mask != AE_NONE) { 100 | epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee); 101 | } else { 102 | /* Note, Kernel < 2.6.9 requires a non null event pointer even for 103 | * EPOLL_CTL_DEL. */ 104 | epoll_ctl(state->epfd,EPOLL_CTL_DEL,fd,&ee); 105 | } 106 | } 107 | 108 | static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { 109 | aeApiState *state = eventLoop->apidata; 110 | int retval, numevents = 0; 111 | 112 | retval = epoll_wait(state->epfd,state->events,eventLoop->setsize, 113 | tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1); 114 | if (retval > 0) { 115 | int j; 116 | 117 | numevents = retval; 118 | for (j = 0; j < numevents; j++) { 119 | int mask = 0; 120 | struct epoll_event *e = state->events+j; 121 | 122 | if (e->events & EPOLLIN) mask |= AE_READABLE; 123 | if (e->events & EPOLLOUT) mask |= AE_WRITABLE; 124 | if (e->events & EPOLLERR) mask |= AE_WRITABLE; 125 | if (e->events & EPOLLHUP) mask |= AE_WRITABLE; 126 | eventLoop->fired[j].fd = e->data.fd; 127 | eventLoop->fired[j].mask = mask; 128 | } 129 | } 130 | return numevents; 131 | } 132 | 133 | static char *aeApiName(void) { 134 | return "epoll"; 135 | } 136 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 re-associate 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 re-associate 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 association 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 int aeApiResize(aeEventLoop *eventLoop, int setsize) { 98 | /* Nothing to resize here. */ 99 | return 0; 100 | } 101 | 102 | static void aeApiFree(aeEventLoop *eventLoop) { 103 | aeApiState *state = eventLoop->apidata; 104 | 105 | close(state->portfd); 106 | zfree(state); 107 | } 108 | 109 | static int aeApiLookupPending(aeApiState *state, int fd) { 110 | int i; 111 | 112 | for (i = 0; i < state->npending; i++) { 113 | if (state->pending_fds[i] == fd) 114 | return (i); 115 | } 116 | 117 | return (-1); 118 | } 119 | 120 | /* 121 | * Helper function to invoke port_associate for the given fd and mask. 122 | */ 123 | static int aeApiAssociate(const char *where, int portfd, int fd, int mask) { 124 | int events = 0; 125 | int rv, err; 126 | 127 | if (mask & AE_READABLE) 128 | events |= POLLIN; 129 | if (mask & AE_WRITABLE) 130 | events |= POLLOUT; 131 | 132 | if (evport_debug) 133 | fprintf(stderr, "%s: port_associate(%d, 0x%x) = ", where, fd, events); 134 | 135 | rv = port_associate(portfd, PORT_SOURCE_FD, fd, events, 136 | (void *)(uintptr_t)mask); 137 | err = errno; 138 | 139 | if (evport_debug) 140 | fprintf(stderr, "%d (%s)\n", rv, rv == 0 ? "no error" : strerror(err)); 141 | 142 | if (rv == -1) { 143 | fprintf(stderr, "%s: port_associate: %s\n", where, strerror(err)); 144 | 145 | if (err == EAGAIN) 146 | fprintf(stderr, "aeApiAssociate: event port limit exceeded."); 147 | } 148 | 149 | return rv; 150 | } 151 | 152 | static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 153 | aeApiState *state = eventLoop->apidata; 154 | int fullmask, pfd; 155 | 156 | if (evport_debug) 157 | fprintf(stderr, "aeApiAddEvent: fd %d mask 0x%x\n", fd, mask); 158 | 159 | /* 160 | * Since port_associate's "events" argument replaces any existing events, we 161 | * must be sure to include whatever events are already associated when 162 | * we call port_associate() again. 163 | */ 164 | fullmask = mask | eventLoop->events[fd].mask; 165 | pfd = aeApiLookupPending(state, fd); 166 | 167 | if (pfd != -1) { 168 | /* 169 | * This fd was recently returned from aeApiPoll. It should be safe to 170 | * assume that the consumer has processed that poll event, but we play 171 | * it safer by simply updating pending_mask. The fd will be 172 | * re-associated as usual when aeApiPoll is called again. 173 | */ 174 | if (evport_debug) 175 | fprintf(stderr, "aeApiAddEvent: adding to pending fd %d\n", fd); 176 | state->pending_masks[pfd] |= fullmask; 177 | return 0; 178 | } 179 | 180 | return (aeApiAssociate("aeApiAddEvent", state->portfd, fd, fullmask)); 181 | } 182 | 183 | static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { 184 | aeApiState *state = eventLoop->apidata; 185 | int fullmask, pfd; 186 | 187 | if (evport_debug) 188 | fprintf(stderr, "del fd %d mask 0x%x\n", fd, mask); 189 | 190 | pfd = aeApiLookupPending(state, fd); 191 | 192 | if (pfd != -1) { 193 | if (evport_debug) 194 | fprintf(stderr, "deleting event from pending fd %d\n", fd); 195 | 196 | /* 197 | * This fd was just returned from aeApiPoll, so it's not currently 198 | * associated with the port. All we need to do is update 199 | * pending_mask appropriately. 200 | */ 201 | state->pending_masks[pfd] &= ~mask; 202 | 203 | if (state->pending_masks[pfd] == AE_NONE) 204 | state->pending_fds[pfd] = -1; 205 | 206 | return; 207 | } 208 | 209 | /* 210 | * The fd is currently associated with the port. Like with the add case 211 | * above, we must look at the full mask for the file descriptor before 212 | * updating that association. We don't have a good way of knowing what the 213 | * events are without looking into the eventLoop state directly. We rely on 214 | * the fact that our caller has already updated the mask in the eventLoop. 215 | */ 216 | 217 | fullmask = eventLoop->events[fd].mask; 218 | if (fullmask == AE_NONE) { 219 | /* 220 | * We're removing *all* events, so use port_dissociate to remove the 221 | * association completely. Failure here indicates a bug. 222 | */ 223 | if (evport_debug) 224 | fprintf(stderr, "aeApiDelEvent: port_dissociate(%d)\n", fd); 225 | 226 | if (port_dissociate(state->portfd, PORT_SOURCE_FD, fd) != 0) { 227 | perror("aeApiDelEvent: port_dissociate"); 228 | abort(); /* will not return */ 229 | } 230 | } else if (aeApiAssociate("aeApiDelEvent", state->portfd, fd, 231 | fullmask) != 0) { 232 | /* 233 | * ENOMEM is a potentially transient condition, but the kernel won't 234 | * generally return it unless things are really bad. EAGAIN indicates 235 | * we've reached an resource limit, for which it doesn't make sense to 236 | * retry (counter-intuitively). All other errors indicate a bug. In any 237 | * of these cases, the best we can do is to abort. 238 | */ 239 | abort(); /* will not return */ 240 | } 241 | } 242 | 243 | static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { 244 | aeApiState *state = eventLoop->apidata; 245 | struct timespec timeout, *tsp; 246 | int mask, i; 247 | uint_t nevents; 248 | port_event_t event[MAX_EVENT_BATCHSZ]; 249 | 250 | /* 251 | * If we've returned fd events before, we must re-associate them with the 252 | * port now, before calling port_get(). See the block comment at the top of 253 | * this file for an explanation of why. 254 | */ 255 | for (i = 0; i < state->npending; i++) { 256 | if (state->pending_fds[i] == -1) 257 | /* This fd has since been deleted. */ 258 | continue; 259 | 260 | if (aeApiAssociate("aeApiPoll", state->portfd, 261 | state->pending_fds[i], state->pending_masks[i]) != 0) { 262 | /* See aeApiDelEvent for why this case is fatal. */ 263 | abort(); 264 | } 265 | 266 | state->pending_masks[i] = AE_NONE; 267 | state->pending_fds[i] = -1; 268 | } 269 | 270 | state->npending = 0; 271 | 272 | if (tvp != NULL) { 273 | timeout.tv_sec = tvp->tv_sec; 274 | timeout.tv_nsec = tvp->tv_usec * 1000; 275 | tsp = &timeout; 276 | } else { 277 | tsp = NULL; 278 | } 279 | 280 | /* 281 | * port_getn can return with errno == ETIME having returned some events (!). 282 | * So if we get ETIME, we check nevents, too. 283 | */ 284 | nevents = 1; 285 | if (port_getn(state->portfd, event, MAX_EVENT_BATCHSZ, &nevents, 286 | tsp) == -1 && (errno != ETIME || nevents == 0)) { 287 | if (errno == ETIME || errno == EINTR) 288 | return 0; 289 | 290 | /* Any other error indicates a bug. */ 291 | perror("aeApiPoll: port_get"); 292 | abort(); 293 | } 294 | 295 | state->npending = nevents; 296 | 297 | for (i = 0; i < nevents; i++) { 298 | mask = 0; 299 | if (event[i].portev_events & POLLIN) 300 | mask |= AE_READABLE; 301 | if (event[i].portev_events & POLLOUT) 302 | mask |= AE_WRITABLE; 303 | 304 | eventLoop->fired[i].fd = event[i].portev_object; 305 | eventLoop->fired[i].mask = mask; 306 | 307 | if (evport_debug) 308 | fprintf(stderr, "aeApiPoll: fd %d mask 0x%x\n", 309 | (int)event[i].portev_object, mask); 310 | 311 | state->pending_fds[i] = event[i].portev_object; 312 | state->pending_masks[i] = (uintptr_t)event[i].portev_user; 313 | } 314 | 315 | return nevents; 316 | } 317 | 318 | static char *aeApiName(void) { 319 | return "evport"; 320 | } 321 | -------------------------------------------------------------------------------- /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 | 34 | /* This function provide us access to the original libc free(). This is useful 35 | * for instance to free results obtained by backtrace_symbols(). We need 36 | * to define this function before including zmalloc.h that may shadow the 37 | * free implementation if we use jemalloc or another non standard allocator. */ 38 | void zlibc_free(void *ptr) { 39 | free(ptr); 40 | } 41 | 42 | #include 43 | #include 44 | #include "config.h" 45 | #include "zmalloc.h" 46 | #include "atomicvar.h" 47 | 48 | #ifdef HAVE_MALLOC_SIZE 49 | #define PREFIX_SIZE (0) 50 | #else 51 | #if defined(__sun) || defined(__sparc) || defined(__sparc__) 52 | #define PREFIX_SIZE (sizeof(long long)) 53 | #else 54 | #define PREFIX_SIZE (sizeof(size_t)) 55 | #endif 56 | #endif 57 | 58 | /* Explicitly override malloc/free etc when using tcmalloc. */ 59 | #if defined(USE_TCMALLOC) 60 | #define malloc(size) tc_malloc(size) 61 | #define calloc(count,size) tc_calloc(count,size) 62 | #define realloc(ptr,size) tc_realloc(ptr,size) 63 | #define free(ptr) tc_free(ptr) 64 | #elif defined(USE_JEMALLOC) 65 | #define malloc(size) je_malloc(size) 66 | #define calloc(count,size) je_calloc(count,size) 67 | #define realloc(ptr,size) je_realloc(ptr,size) 68 | #define free(ptr) je_free(ptr) 69 | #define mallocx(size,flags) je_mallocx(size,flags) 70 | #define dallocx(ptr,flags) je_dallocx(ptr,flags) 71 | #endif 72 | 73 | #define update_zmalloc_stat_alloc(__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 | atomicIncr(used_memory,__n,used_memory_mutex); \ 78 | } else { \ 79 | used_memory += _n; \ 80 | } \ 81 | } while(0) 82 | 83 | #define update_zmalloc_stat_free(__n) do { \ 84 | size_t _n = (__n); \ 85 | if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ 86 | if (zmalloc_thread_safe) { \ 87 | atomicDecr(used_memory,__n,used_memory_mutex); \ 88 | } else { \ 89 | used_memory -= _n; \ 90 | } \ 91 | } while(0) 92 | 93 | static size_t used_memory = 0; 94 | static int zmalloc_thread_safe = 0; 95 | pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER; 96 | 97 | static void zmalloc_default_oom(size_t size) { 98 | fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n", 99 | size); 100 | fflush(stderr); 101 | abort(); 102 | } 103 | 104 | static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom; 105 | 106 | void *zmalloc(size_t size) { 107 | void *ptr = malloc(size+PREFIX_SIZE); 108 | 109 | if (!ptr) zmalloc_oom_handler(size); 110 | #ifdef HAVE_MALLOC_SIZE 111 | update_zmalloc_stat_alloc(zmalloc_size(ptr)); 112 | return ptr; 113 | #else 114 | *((size_t*)ptr) = size; 115 | update_zmalloc_stat_alloc(size+PREFIX_SIZE); 116 | return (char*)ptr+PREFIX_SIZE; 117 | #endif 118 | } 119 | 120 | /* Allocation and free functions that bypass the thread cache 121 | * and go straight to the allocator arena bins. 122 | * Currently implemented only for jemalloc. Used for online defragmentation. */ 123 | #ifdef HAVE_DEFRAG 124 | void *zmalloc_no_tcache(size_t size) { 125 | void *ptr = mallocx(size+PREFIX_SIZE, MALLOCX_TCACHE_NONE); 126 | if (!ptr) zmalloc_oom_handler(size); 127 | update_zmalloc_stat_alloc(zmalloc_size(ptr)); 128 | return ptr; 129 | } 130 | 131 | void zfree_no_tcache(void *ptr) { 132 | if (ptr == NULL) return; 133 | update_zmalloc_stat_free(zmalloc_size(ptr)); 134 | dallocx(ptr, MALLOCX_TCACHE_NONE); 135 | } 136 | #endif 137 | 138 | void *zcalloc(size_t size) { 139 | void *ptr = calloc(1, size+PREFIX_SIZE); 140 | 141 | if (!ptr) zmalloc_oom_handler(size); 142 | #ifdef HAVE_MALLOC_SIZE 143 | update_zmalloc_stat_alloc(zmalloc_size(ptr)); 144 | return ptr; 145 | #else 146 | *((size_t*)ptr) = size; 147 | update_zmalloc_stat_alloc(size+PREFIX_SIZE); 148 | return (char*)ptr+PREFIX_SIZE; 149 | #endif 150 | } 151 | 152 | void *zrealloc(void *ptr, size_t size) { 153 | #ifndef HAVE_MALLOC_SIZE 154 | void *realptr; 155 | #endif 156 | size_t oldsize; 157 | void *newptr; 158 | 159 | if (ptr == NULL) return zmalloc(size); 160 | #ifdef HAVE_MALLOC_SIZE 161 | oldsize = zmalloc_size(ptr); 162 | newptr = realloc(ptr,size); 163 | if (!newptr) zmalloc_oom_handler(size); 164 | 165 | update_zmalloc_stat_free(oldsize); 166 | update_zmalloc_stat_alloc(zmalloc_size(newptr)); 167 | return newptr; 168 | #else 169 | realptr = (char*)ptr-PREFIX_SIZE; 170 | oldsize = *((size_t*)realptr); 171 | newptr = realloc(realptr,size+PREFIX_SIZE); 172 | if (!newptr) zmalloc_oom_handler(size); 173 | 174 | *((size_t*)newptr) = size; 175 | update_zmalloc_stat_free(oldsize); 176 | update_zmalloc_stat_alloc(size); 177 | return (char*)newptr+PREFIX_SIZE; 178 | #endif 179 | } 180 | 181 | /* Provide zmalloc_size() for systems where this function is not provided by 182 | * malloc itself, given that in that case we store a header with this 183 | * information as the first bytes of every allocation. */ 184 | #ifndef HAVE_MALLOC_SIZE 185 | size_t zmalloc_size(void *ptr) { 186 | void *realptr = (char*)ptr-PREFIX_SIZE; 187 | size_t size = *((size_t*)realptr); 188 | /* Assume at least that all the allocations are padded at sizeof(long) by 189 | * the underlying allocator. */ 190 | if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1)); 191 | return size+PREFIX_SIZE; 192 | } 193 | #endif 194 | 195 | void zfree(void *ptr) { 196 | #ifndef HAVE_MALLOC_SIZE 197 | void *realptr; 198 | size_t oldsize; 199 | #endif 200 | 201 | if (ptr == NULL) return; 202 | #ifdef HAVE_MALLOC_SIZE 203 | update_zmalloc_stat_free(zmalloc_size(ptr)); 204 | free(ptr); 205 | #else 206 | realptr = (char*)ptr-PREFIX_SIZE; 207 | oldsize = *((size_t*)realptr); 208 | update_zmalloc_stat_free(oldsize+PREFIX_SIZE); 209 | free(realptr); 210 | #endif 211 | } 212 | 213 | char *zstrdup(const char *s) { 214 | size_t l = strlen(s)+1; 215 | char *p = zmalloc(l); 216 | 217 | memcpy(p,s,l); 218 | return p; 219 | } 220 | 221 | size_t zmalloc_used_memory(void) { 222 | size_t um; 223 | 224 | if (zmalloc_thread_safe) { 225 | atomicGet(used_memory,um,used_memory_mutex); 226 | } else { 227 | um = used_memory; 228 | } 229 | return um; 230 | } 231 | 232 | void zmalloc_enable_thread_safeness(void) { 233 | zmalloc_thread_safe = 1; 234 | } 235 | 236 | void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) { 237 | zmalloc_oom_handler = oom_handler; 238 | } 239 | 240 | /* Get the RSS information in an OS-specific way. 241 | * 242 | * WARNING: the function zmalloc_get_rss() is not designed to be fast 243 | * and may not be called in the busy loops where Redis tries to release 244 | * memory expiring or swapping out objects. 245 | * 246 | * For this kind of "fast RSS reporting" usages use instead the 247 | * function RedisEstimateRSS() that is a much faster (and less precise) 248 | * version of the function. */ 249 | 250 | #if defined(HAVE_PROC_STAT) 251 | #include 252 | #include 253 | #include 254 | #include 255 | 256 | size_t zmalloc_get_rss(void) { 257 | int page = sysconf(_SC_PAGESIZE); 258 | size_t rss; 259 | char buf[4096]; 260 | char filename[256]; 261 | int fd, count; 262 | char *p, *x; 263 | 264 | snprintf(filename,256,"/proc/%d/stat",getpid()); 265 | if ((fd = open(filename,O_RDONLY)) == -1) return 0; 266 | if (read(fd,buf,4096) <= 0) { 267 | close(fd); 268 | return 0; 269 | } 270 | close(fd); 271 | 272 | p = buf; 273 | count = 23; /* RSS is the 24th field in /proc//stat */ 274 | while(p && count--) { 275 | p = strchr(p,' '); 276 | if (p) p++; 277 | } 278 | if (!p) return 0; 279 | x = strchr(p,' '); 280 | if (!x) return 0; 281 | *x = '\0'; 282 | 283 | rss = strtoll(p,NULL,10); 284 | rss *= page; 285 | return rss; 286 | } 287 | #elif defined(HAVE_TASKINFO) 288 | #include 289 | #include 290 | #include 291 | #include 292 | #include 293 | #include 294 | #include 295 | 296 | size_t zmalloc_get_rss(void) { 297 | task_t task = MACH_PORT_NULL; 298 | struct task_basic_info t_info; 299 | mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; 300 | 301 | if (task_for_pid(current_task(), getpid(), &task) != KERN_SUCCESS) 302 | return 0; 303 | task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); 304 | 305 | return t_info.resident_size; 306 | } 307 | #else 308 | size_t zmalloc_get_rss(void) { 309 | /* If we can't get the RSS in an OS-specific way for this system just 310 | * return the memory usage we estimated in zmalloc().. 311 | * 312 | * Fragmentation will appear to be always 1 (no fragmentation) 313 | * of course... */ 314 | return zmalloc_used_memory(); 315 | } 316 | #endif 317 | 318 | /* Fragmentation = RSS / allocated-bytes */ 319 | float zmalloc_get_fragmentation_ratio(size_t rss) { 320 | return (float)rss/zmalloc_used_memory(); 321 | } 322 | 323 | /* Get the sum of the specified field (converted form kb to bytes) in 324 | * /proc/self/smaps. The field must be specified with trailing ":" as it 325 | * apperas in the smaps output. 326 | * 327 | * If a pid is specified, the information is extracted for such a pid, 328 | * otherwise if pid is -1 the information is reported is about the 329 | * current process. 330 | * 331 | * Example: zmalloc_get_smap_bytes_by_field("Rss:",-1); 332 | */ 333 | #if defined(HAVE_PROC_SMAPS) 334 | size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) { 335 | char line[1024]; 336 | size_t bytes = 0; 337 | int flen = strlen(field); 338 | FILE *fp; 339 | 340 | if (pid == -1) { 341 | fp = fopen("/proc/self/smaps","r"); 342 | } else { 343 | char filename[128]; 344 | snprintf(filename,sizeof(filename),"/proc/%ld/smaps",pid); 345 | fp = fopen(filename,"r"); 346 | } 347 | 348 | if (!fp) return 0; 349 | while(fgets(line,sizeof(line),fp) != NULL) { 350 | if (strncmp(line,field,flen) == 0) { 351 | char *p = strchr(line,'k'); 352 | if (p) { 353 | *p = '\0'; 354 | bytes += strtol(line+flen,NULL,10) * 1024; 355 | } 356 | } 357 | } 358 | fclose(fp); 359 | return bytes; 360 | } 361 | #else 362 | size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) { 363 | ((void) field); 364 | ((void) pid); 365 | return 0; 366 | } 367 | #endif 368 | 369 | size_t zmalloc_get_private_dirty(long pid) { 370 | return zmalloc_get_smap_bytes_by_field("Private_Dirty:",pid); 371 | } 372 | 373 | /* Returns the size of physical memory (RAM) in bytes. 374 | * It looks ugly, but this is the cleanest way to achive cross platform results. 375 | * Cleaned up from: 376 | * 377 | * http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system 378 | * 379 | * Note that this function: 380 | * 1) Was released under the following CC attribution license: 381 | * http://creativecommons.org/licenses/by/3.0/deed.en_US. 382 | * 2) Was originally implemented by David Robert Nadeau. 383 | * 3) Was modified for Redis by Matt Stancliff. 384 | * 4) This note exists in order to comply with the original license. 385 | */ 386 | size_t zmalloc_get_memory_size(void) { 387 | #if defined(__unix__) || defined(__unix) || defined(unix) || \ 388 | (defined(__APPLE__) && defined(__MACH__)) 389 | #if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64)) 390 | int mib[2]; 391 | mib[0] = CTL_HW; 392 | #if defined(HW_MEMSIZE) 393 | mib[1] = HW_MEMSIZE; /* OSX. --------------------- */ 394 | #elif defined(HW_PHYSMEM64) 395 | mib[1] = HW_PHYSMEM64; /* NetBSD, OpenBSD. --------- */ 396 | #endif 397 | int64_t size = 0; /* 64-bit */ 398 | size_t len = sizeof(size); 399 | if (sysctl( mib, 2, &size, &len, NULL, 0) == 0) 400 | return (size_t)size; 401 | return 0L; /* Failed? */ 402 | 403 | #elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE) 404 | /* FreeBSD, Linux, OpenBSD, and Solaris. -------------------- */ 405 | return (size_t)sysconf(_SC_PHYS_PAGES) * (size_t)sysconf(_SC_PAGESIZE); 406 | 407 | #elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM)) 408 | /* DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. -------- */ 409 | int mib[2]; 410 | mib[0] = CTL_HW; 411 | #if defined(HW_REALMEM) 412 | mib[1] = HW_REALMEM; /* FreeBSD. ----------------- */ 413 | #elif defined(HW_PYSMEM) 414 | mib[1] = HW_PHYSMEM; /* Others. ------------------ */ 415 | #endif 416 | unsigned int size = 0; /* 32-bit */ 417 | size_t len = sizeof(size); 418 | if (sysctl(mib, 2, &size, &len, NULL, 0) == 0) 419 | return (size_t)size; 420 | return 0L; /* Failed? */ 421 | #endif /* sysctl and sysconf variants */ 422 | 423 | #else 424 | return 0L; /* Unknown OS. */ 425 | #endif 426 | } 427 | 428 | 429 | -------------------------------------------------------------------------------- /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 | /* Return the current set size. */ 95 | int aeGetSetSize(aeEventLoop *eventLoop) { 96 | return eventLoop->setsize; 97 | } 98 | 99 | /* Resize the maximum set size of the event loop. 100 | * If the requested set size is smaller than the current set size, but 101 | * there is already a file descriptor in use that is >= the requested 102 | * set size minus one, AE_ERR is returned and the operation is not 103 | * performed at all. 104 | * 105 | * Otherwise AE_OK is returned and the operation is successful. */ 106 | int aeResizeSetSize(aeEventLoop *eventLoop, int setsize) { 107 | int i; 108 | 109 | if (setsize == eventLoop->setsize) return AE_OK; 110 | if (eventLoop->maxfd >= setsize) return AE_ERR; 111 | if (aeApiResize(eventLoop,setsize) == -1) return AE_ERR; 112 | 113 | eventLoop->events = zrealloc(eventLoop->events,sizeof(aeFileEvent)*setsize); 114 | eventLoop->fired = zrealloc(eventLoop->fired,sizeof(aeFiredEvent)*setsize); 115 | eventLoop->setsize = setsize; 116 | 117 | /* Make sure that if we created new slots, they are initialized with 118 | * an AE_NONE mask. */ 119 | for (i = eventLoop->maxfd+1; i < setsize; i++) 120 | eventLoop->events[i].mask = AE_NONE; 121 | return AE_OK; 122 | } 123 | 124 | void aeDeleteEventLoop(aeEventLoop *eventLoop) { 125 | aeApiFree(eventLoop); 126 | zfree(eventLoop->events); 127 | zfree(eventLoop->fired); 128 | zfree(eventLoop); 129 | } 130 | 131 | void aeStop(aeEventLoop *eventLoop) { 132 | eventLoop->stop = 1; 133 | } 134 | 135 | int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, 136 | aeFileProc *proc, void *clientData) 137 | { 138 | if (fd >= eventLoop->setsize) { 139 | errno = ERANGE; 140 | printf(" fd(%d) >= set size(%d) \n", fd, eventLoop->setsize); 141 | return AE_ERR; 142 | } 143 | aeFileEvent *fe = &eventLoop->events[fd]; 144 | 145 | if (aeApiAddEvent(eventLoop, fd, mask) == -1) 146 | return AE_ERR; 147 | fe->mask |= mask; 148 | if (mask & AE_READABLE) fe->rfileProc = proc; 149 | if (mask & AE_WRITABLE) fe->wfileProc = proc; 150 | fe->clientData = clientData; 151 | if (fd > eventLoop->maxfd) 152 | eventLoop->maxfd = fd; 153 | return AE_OK; 154 | } 155 | 156 | void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask) 157 | { 158 | if (fd >= eventLoop->setsize) return; 159 | aeFileEvent *fe = &eventLoop->events[fd]; 160 | if (fe->mask == AE_NONE) return; 161 | 162 | aeApiDelEvent(eventLoop, fd, mask); 163 | fe->mask = fe->mask & (~mask); 164 | if (fd == eventLoop->maxfd && fe->mask == AE_NONE) { 165 | /* Update the max fd */ 166 | int j; 167 | 168 | for (j = eventLoop->maxfd-1; j >= 0; j--) 169 | if (eventLoop->events[j].mask != AE_NONE) break; 170 | eventLoop->maxfd = j; 171 | } 172 | } 173 | 174 | int aeGetFileEvents(aeEventLoop *eventLoop, int fd) { 175 | if (fd >= eventLoop->setsize) return 0; 176 | aeFileEvent *fe = &eventLoop->events[fd]; 177 | 178 | return fe->mask; 179 | } 180 | 181 | static void aeGetTime(long *seconds, long *milliseconds) 182 | { 183 | struct timeval tv; 184 | 185 | gettimeofday(&tv, NULL); 186 | *seconds = tv.tv_sec; 187 | *milliseconds = tv.tv_usec/1000; 188 | } 189 | 190 | static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) { 191 | long cur_sec, cur_ms, when_sec, when_ms; 192 | 193 | aeGetTime(&cur_sec, &cur_ms); 194 | when_sec = cur_sec + milliseconds/1000; 195 | when_ms = cur_ms + milliseconds%1000; 196 | if (when_ms >= 1000) { 197 | when_sec ++; 198 | when_ms -= 1000; 199 | } 200 | *sec = when_sec; 201 | *ms = when_ms; 202 | } 203 | 204 | long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, 205 | aeTimeProc *proc, void *clientData, 206 | aeEventFinalizerProc *finalizerProc) 207 | { 208 | long long id = eventLoop->timeEventNextId++; 209 | aeTimeEvent *te; 210 | 211 | te = zmalloc(sizeof(*te)); 212 | if (te == NULL) return AE_ERR; 213 | te->id = id; 214 | aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms); 215 | te->timeProc = proc; 216 | te->finalizerProc = finalizerProc; 217 | te->clientData = clientData; 218 | te->next = eventLoop->timeEventHead; 219 | eventLoop->timeEventHead = te; 220 | return id; 221 | } 222 | 223 | int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id) 224 | { 225 | aeTimeEvent *te = eventLoop->timeEventHead; 226 | while(te) { 227 | if (te->id == id) { 228 | te->id = AE_DELETED_EVENT_ID; 229 | return AE_OK; 230 | } 231 | te = te->next; 232 | } 233 | return AE_ERR; /* NO event with the specified ID found */ 234 | } 235 | 236 | /* Search the first timer to fire. 237 | * This operation is useful to know how many time the select can be 238 | * put in sleep without to delay any event. 239 | * If there are no timers NULL is returned. 240 | * 241 | * Note that's O(N) since time events are unsorted. 242 | * Possible optimizations (not needed by Redis so far, but...): 243 | * 1) Insert the event in order, so that the nearest is just the head. 244 | * Much better but still insertion or deletion of timers is O(N). 245 | * 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)). 246 | */ 247 | static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop) 248 | { 249 | aeTimeEvent *te = eventLoop->timeEventHead; 250 | aeTimeEvent *nearest = NULL; 251 | 252 | while(te) { 253 | if (!nearest || te->when_sec < nearest->when_sec || 254 | (te->when_sec == nearest->when_sec && 255 | te->when_ms < nearest->when_ms)) 256 | nearest = te; 257 | te = te->next; 258 | } 259 | return nearest; 260 | } 261 | 262 | /* Process time events */ 263 | static int processTimeEvents(aeEventLoop *eventLoop) { 264 | int processed = 0; 265 | aeTimeEvent *te, *prev; 266 | long long maxId; 267 | time_t now = time(NULL); 268 | 269 | /* If the system clock is moved to the future, and then set back to the 270 | * right value, time events may be delayed in a random way. Often this 271 | * means that scheduled operations will not be performed soon enough. 272 | * 273 | * Here we try to detect system clock skews, and force all the time 274 | * events to be processed ASAP when this happens: the idea is that 275 | * processing events earlier is less dangerous than delaying them 276 | * indefinitely, and practice suggests it is. */ 277 | if (now < eventLoop->lastTime) { 278 | te = eventLoop->timeEventHead; 279 | while(te) { 280 | te->when_sec = 0; 281 | te = te->next; 282 | } 283 | } 284 | eventLoop->lastTime = now; 285 | 286 | prev = NULL; 287 | te = eventLoop->timeEventHead; 288 | maxId = eventLoop->timeEventNextId-1; 289 | while(te) { 290 | long now_sec, now_ms; 291 | long long id; 292 | 293 | /* Remove events scheduled for deletion. */ 294 | if (te->id == AE_DELETED_EVENT_ID) { 295 | aeTimeEvent *next = te->next; 296 | if (prev == NULL) 297 | eventLoop->timeEventHead = te->next; 298 | else 299 | prev->next = te->next; 300 | if (te->finalizerProc) 301 | te->finalizerProc(eventLoop, te->clientData); 302 | zfree(te); 303 | te = next; 304 | continue; 305 | } 306 | 307 | /* Make sure we don't process time events created by time events in 308 | * this iteration. Note that this check is currently useless: we always 309 | * add new timers on the head, however if we change the implementation 310 | * detail, this check may be useful again: we keep it here for future 311 | * defense. */ 312 | if (te->id > maxId) { 313 | te = te->next; 314 | continue; 315 | } 316 | aeGetTime(&now_sec, &now_ms); 317 | if (now_sec > te->when_sec || 318 | (now_sec == te->when_sec && now_ms >= te->when_ms)) 319 | { 320 | int retval; 321 | 322 | id = te->id; 323 | retval = te->timeProc(eventLoop, id, te->clientData); 324 | processed++; 325 | if (retval != AE_NOMORE) { 326 | aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms); 327 | } else { 328 | te->id = AE_DELETED_EVENT_ID; 329 | } 330 | } 331 | prev = te; 332 | te = te->next; 333 | } 334 | return processed; 335 | } 336 | 337 | /* Process every pending time event, then every pending file event 338 | * (that may be registered by time event callbacks just processed). 339 | * Without special flags the function sleeps until some file event 340 | * fires, or when the next time event occurs (if any). 341 | * 342 | * If flags is 0, the function does nothing and returns. 343 | * if flags has AE_ALL_EVENTS set, all the kind of events are processed. 344 | * if flags has AE_FILE_EVENTS set, file events are processed. 345 | * if flags has AE_TIME_EVENTS set, time events are processed. 346 | * if flags has AE_DONT_WAIT set the function returns ASAP until all 347 | * the events that's possible to process without to wait are processed. 348 | * 349 | * The function returns the number of events processed. */ 350 | int aeProcessEvents(aeEventLoop *eventLoop, int flags) 351 | { 352 | int processed = 0, numevents; 353 | 354 | /* Nothing to do? return ASAP */ 355 | if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0; 356 | 357 | /* Note that we want call select() even if there are no 358 | * file events to process as long as we want to process time 359 | * events, in order to sleep until the next time event is ready 360 | * to fire. */ 361 | if (eventLoop->maxfd != -1 || 362 | ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { 363 | int j; 364 | aeTimeEvent *shortest = NULL; 365 | struct timeval tv, *tvp; 366 | 367 | if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT)) 368 | shortest = aeSearchNearestTimer(eventLoop); 369 | if (shortest) { 370 | long now_sec, now_ms; 371 | 372 | aeGetTime(&now_sec, &now_ms); 373 | tvp = &tv; 374 | 375 | /* How many milliseconds we need to wait for the next 376 | * time event to fire? */ 377 | long long ms = 378 | (shortest->when_sec - now_sec)*1000 + 379 | shortest->when_ms - now_ms; 380 | 381 | if (ms > 0) { 382 | tvp->tv_sec = ms/1000; 383 | tvp->tv_usec = (ms % 1000)*1000; 384 | } else { 385 | tvp->tv_sec = 0; 386 | tvp->tv_usec = 0; 387 | } 388 | } else { 389 | /* If we have to check for events but need to return 390 | * ASAP because of AE_DONT_WAIT we need to set the timeout 391 | * to zero */ 392 | if (flags & AE_DONT_WAIT) { 393 | tv.tv_sec = tv.tv_usec = 0; 394 | tvp = &tv; 395 | } else { 396 | /* Otherwise we can block */ 397 | tvp = NULL; /* wait forever */ 398 | } 399 | } 400 | 401 | numevents = aeApiPoll(eventLoop, tvp); 402 | for (j = 0; j < numevents; j++) { 403 | aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd]; 404 | int mask = eventLoop->fired[j].mask; 405 | int fd = eventLoop->fired[j].fd; 406 | int rfired = 0; 407 | 408 | /* note the fe->mask & mask & ... code: maybe an already processed 409 | * event removed an element that fired and we still didn't 410 | * processed, so we check if the event is still valid. */ 411 | if (fe->mask & mask & AE_READABLE) { 412 | rfired = 1; 413 | fe->rfileProc(eventLoop,fd,fe->clientData,mask); 414 | } 415 | if (fe->mask & mask & AE_WRITABLE) { 416 | if (!rfired || fe->wfileProc != fe->rfileProc) 417 | fe->wfileProc(eventLoop,fd,fe->clientData,mask); 418 | } 419 | processed++; 420 | } 421 | } 422 | /* Check time events */ 423 | if (flags & AE_TIME_EVENTS) 424 | processed += processTimeEvents(eventLoop); 425 | 426 | return processed; /* return the number of processed file/time events */ 427 | } 428 | 429 | /* Wait for milliseconds until the given file descriptor becomes 430 | * writable/readable/exception */ 431 | int aeWait(int fd, int mask, long long milliseconds) { 432 | struct pollfd pfd; 433 | int retmask = 0, retval; 434 | 435 | memset(&pfd, 0, sizeof(pfd)); 436 | pfd.fd = fd; 437 | if (mask & AE_READABLE) pfd.events |= POLLIN; 438 | if (mask & AE_WRITABLE) pfd.events |= POLLOUT; 439 | 440 | if ((retval = poll(&pfd, 1, milliseconds))== 1) { 441 | if (pfd.revents & POLLIN) retmask |= AE_READABLE; 442 | if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE; 443 | if (pfd.revents & POLLERR) retmask |= AE_WRITABLE; 444 | if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE; 445 | return retmask; 446 | } else { 447 | return retval; 448 | } 449 | } 450 | 451 | void aeMain(aeEventLoop *eventLoop) { 452 | eventLoop->stop = 0; 453 | while (!eventLoop->stop) { 454 | if (eventLoop->beforesleep != NULL) 455 | eventLoop->beforesleep(eventLoop); 456 | aeProcessEvents(eventLoop, AE_ALL_EVENTS); 457 | } 458 | } 459 | 460 | char *aeGetApiName(void) { 461 | return aeApiName(); 462 | } 463 | 464 | void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep) { 465 | eventLoop->beforesleep = beforesleep; 466 | } 467 | -------------------------------------------------------------------------------- /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 | int ret; 470 | int count = 0; 471 | if ((fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) != -1) { 472 | while(count < 5){ 473 | ret = connect(fd, addr->ai_addr, addr->ai_addrlen); 474 | if(ret == 0) 475 | break; 476 | else 477 | sleep(1); 478 | count++; 479 | } 480 | connected = ret == 0; 481 | close(fd); 482 | } 483 | lua_pushboolean(L, connected); 484 | return 1; 485 | } 486 | 487 | void script_copy_value(lua_State *src, lua_State *dst, int index) { 488 | switch (lua_type(src, index)) { 489 | case LUA_TBOOLEAN: 490 | lua_pushboolean(dst, lua_toboolean(src, index)); 491 | break; 492 | case LUA_TNIL: 493 | lua_pushnil(dst); 494 | break; 495 | case LUA_TNUMBER: 496 | lua_pushnumber(dst, lua_tonumber(src, index)); 497 | break; 498 | case LUA_TSTRING: 499 | lua_pushstring(dst, lua_tostring(src, index)); 500 | break; 501 | case LUA_TTABLE: 502 | lua_newtable(dst); 503 | lua_pushnil(src); 504 | while (lua_next(src, index - 1)) { 505 | script_copy_value(src, dst, -2); 506 | script_copy_value(src, dst, -1); 507 | lua_settable(dst, -3); 508 | lua_pop(src, 1); 509 | } 510 | lua_pop(src, 1); 511 | break; 512 | default: 513 | luaL_error(src, "cannot transfer '%s' to thread", luaL_typename(src, index)); 514 | } 515 | } 516 | 517 | int script_parse_url(char *url, struct http_parser_url *parts) { 518 | if (!http_parser_parse_url(url, strlen(url), 0, parts)) { 519 | if (!(parts->field_set & (1 << UF_SCHEMA))) return 0; 520 | if (!(parts->field_set & (1 << UF_HOST))) return 0; 521 | return 1; 522 | } 523 | return 0; 524 | } 525 | 526 | static int push_url_part(lua_State *L, char *url, struct http_parser_url *parts, enum http_parser_url_fields field) { 527 | int type = parts->field_set & (1 << field) ? LUA_TSTRING : LUA_TNIL; 528 | uint16_t off, len; 529 | switch (type) { 530 | case LUA_TSTRING: 531 | off = parts->field_data[field].off; 532 | len = parts->field_data[field].len; 533 | lua_pushlstring(L, &url[off], len); 534 | break; 535 | case LUA_TNIL: 536 | lua_pushnil(L); 537 | } 538 | return type; 539 | } 540 | 541 | static void set_field(lua_State *L, int index, char *field, int type) { 542 | (void) type; 543 | lua_setfield(L, index, field); 544 | } 545 | 546 | static void set_fields(lua_State *L, int index, const table_field *fields) { 547 | for (int i = 0; fields[i].name; i++) { 548 | table_field f = fields[i]; 549 | switch (f.value == NULL ? LUA_TNIL : f.type) { 550 | case LUA_TFUNCTION: 551 | lua_pushcfunction(L, (lua_CFunction) f.value); 552 | break; 553 | case LUA_TNUMBER: 554 | lua_pushinteger(L, *((lua_Integer *) f.value)); 555 | break; 556 | case LUA_TSTRING: 557 | lua_pushstring(L, (const char *) f.value); 558 | break; 559 | case LUA_TNIL: 560 | lua_pushnil(L); 561 | break; 562 | } 563 | lua_setfield(L, index, f.name); 564 | } 565 | } 566 | 567 | void buffer_append(buffer *b, const char *data, size_t len) { 568 | size_t used = b->cursor - b->buffer; 569 | while (used + len + 1 >= b->length) { 570 | b->length += 1024; 571 | b->buffer = realloc(b->buffer, b->length); 572 | b->cursor = b->buffer + used; 573 | } 574 | memcpy(b->cursor, data, len); 575 | b->cursor += len; 576 | } 577 | 578 | void buffer_reset(buffer *b) { 579 | b->cursor = b->buffer; 580 | } 581 | 582 | char *buffer_pushlstring(lua_State *L, char *start) { 583 | char *end = strchr(start, 0); 584 | lua_pushlstring(L, start, end - start); 585 | return end + 1; 586 | } 587 | -------------------------------------------------------------------------------- /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 7 30 | #define HTTP_PARSER_VERSION_PATCH 1 31 | 32 | #include 33 | #if defined(_WIN32) && !defined(__MINGW32__) && \ 34 | (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) 35 | #include 36 | #include 37 | typedef __int8 int8_t; 38 | typedef unsigned __int8 uint8_t; 39 | typedef __int16 int16_t; 40 | typedef unsigned __int16 uint16_t; 41 | typedef __int32 int32_t; 42 | typedef unsigned __int32 uint32_t; 43 | typedef __int64 int64_t; 44 | typedef unsigned __int64 uint64_t; 45 | #else 46 | #include 47 | #endif 48 | 49 | /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run 50 | * faster 51 | */ 52 | #ifndef HTTP_PARSER_STRICT 53 | # define HTTP_PARSER_STRICT 1 54 | #endif 55 | 56 | /* Maximium header size allowed. If the macro is not defined 57 | * before including this header then the default is used. To 58 | * change the maximum header size, define the macro in the build 59 | * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove 60 | * the effective limit on the size of the header, define the macro 61 | * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) 62 | */ 63 | #ifndef HTTP_MAX_HEADER_SIZE 64 | # define HTTP_MAX_HEADER_SIZE (80*1024) 65 | #endif 66 | 67 | typedef struct http_parser http_parser; 68 | typedef struct http_parser_settings http_parser_settings; 69 | 70 | 71 | /* Callbacks should return non-zero to indicate an error. The parser will 72 | * then halt execution. 73 | * 74 | * The one exception is on_headers_complete. In a HTTP_RESPONSE parser 75 | * returning '1' from on_headers_complete will tell the parser that it 76 | * should not expect a body. This is used when receiving a response to a 77 | * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: 78 | * chunked' headers that indicate the presence of a body. 79 | * 80 | * Returning `2` from on_headers_complete will tell parser that it should not 81 | * expect neither a body nor any futher responses on this connection. This is 82 | * useful for handling responses to a CONNECT request which may not contain 83 | * `Upgrade` or `Connection: upgrade` headers. 84 | * 85 | * http_data_cb does not return data chunks. It will be called arbitrarily 86 | * many times for each string. E.G. you might get 10 callbacks for "on_url" 87 | * each providing just a few characters more data. 88 | */ 89 | typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); 90 | typedef int (*http_cb) (http_parser*); 91 | 92 | 93 | /* Status Codes */ 94 | #define HTTP_STATUS_MAP(XX) \ 95 | XX(100, CONTINUE, Continue) \ 96 | XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \ 97 | XX(102, PROCESSING, Processing) \ 98 | XX(200, OK, OK) \ 99 | XX(201, CREATED, Created) \ 100 | XX(202, ACCEPTED, Accepted) \ 101 | XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \ 102 | XX(204, NO_CONTENT, No Content) \ 103 | XX(205, RESET_CONTENT, Reset Content) \ 104 | XX(206, PARTIAL_CONTENT, Partial Content) \ 105 | XX(207, MULTI_STATUS, Multi-Status) \ 106 | XX(208, ALREADY_REPORTED, Already Reported) \ 107 | XX(226, IM_USED, IM Used) \ 108 | XX(300, MULTIPLE_CHOICES, Multiple Choices) \ 109 | XX(301, MOVED_PERMANENTLY, Moved Permanently) \ 110 | XX(302, FOUND, Found) \ 111 | XX(303, SEE_OTHER, See Other) \ 112 | XX(304, NOT_MODIFIED, Not Modified) \ 113 | XX(305, USE_PROXY, Use Proxy) \ 114 | XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \ 115 | XX(308, PERMANENT_REDIRECT, Permanent Redirect) \ 116 | XX(400, BAD_REQUEST, Bad Request) \ 117 | XX(401, UNAUTHORIZED, Unauthorized) \ 118 | XX(402, PAYMENT_REQUIRED, Payment Required) \ 119 | XX(403, FORBIDDEN, Forbidden) \ 120 | XX(404, NOT_FOUND, Not Found) \ 121 | XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \ 122 | XX(406, NOT_ACCEPTABLE, Not Acceptable) \ 123 | XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \ 124 | XX(408, REQUEST_TIMEOUT, Request Timeout) \ 125 | XX(409, CONFLICT, Conflict) \ 126 | XX(410, GONE, Gone) \ 127 | XX(411, LENGTH_REQUIRED, Length Required) \ 128 | XX(412, PRECONDITION_FAILED, Precondition Failed) \ 129 | XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \ 130 | XX(414, URI_TOO_LONG, URI Too Long) \ 131 | XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \ 132 | XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \ 133 | XX(417, EXPECTATION_FAILED, Expectation Failed) \ 134 | XX(421, MISDIRECTED_REQUEST, Misdirected Request) \ 135 | XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \ 136 | XX(423, LOCKED, Locked) \ 137 | XX(424, FAILED_DEPENDENCY, Failed Dependency) \ 138 | XX(426, UPGRADE_REQUIRED, Upgrade Required) \ 139 | XX(428, PRECONDITION_REQUIRED, Precondition Required) \ 140 | XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ 141 | XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ 142 | XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ 143 | XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ 144 | XX(501, NOT_IMPLEMENTED, Not Implemented) \ 145 | XX(502, BAD_GATEWAY, Bad Gateway) \ 146 | XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \ 147 | XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \ 148 | XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \ 149 | XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \ 150 | XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ 151 | XX(508, LOOP_DETECTED, Loop Detected) \ 152 | XX(510, NOT_EXTENDED, Not Extended) \ 153 | XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \ 154 | 155 | enum http_status 156 | { 157 | #define XX(num, name, string) HTTP_STATUS_##name = num, 158 | HTTP_STATUS_MAP(XX) 159 | #undef XX 160 | }; 161 | 162 | 163 | /* Request Methods */ 164 | #define HTTP_METHOD_MAP(XX) \ 165 | XX(0, DELETE, DELETE) \ 166 | XX(1, GET, GET) \ 167 | XX(2, HEAD, HEAD) \ 168 | XX(3, POST, POST) \ 169 | XX(4, PUT, PUT) \ 170 | /* pathological */ \ 171 | XX(5, CONNECT, CONNECT) \ 172 | XX(6, OPTIONS, OPTIONS) \ 173 | XX(7, TRACE, TRACE) \ 174 | /* WebDAV */ \ 175 | XX(8, COPY, COPY) \ 176 | XX(9, LOCK, LOCK) \ 177 | XX(10, MKCOL, MKCOL) \ 178 | XX(11, MOVE, MOVE) \ 179 | XX(12, PROPFIND, PROPFIND) \ 180 | XX(13, PROPPATCH, PROPPATCH) \ 181 | XX(14, SEARCH, SEARCH) \ 182 | XX(15, UNLOCK, UNLOCK) \ 183 | XX(16, BIND, BIND) \ 184 | XX(17, REBIND, REBIND) \ 185 | XX(18, UNBIND, UNBIND) \ 186 | XX(19, ACL, ACL) \ 187 | /* subversion */ \ 188 | XX(20, REPORT, REPORT) \ 189 | XX(21, MKACTIVITY, MKACTIVITY) \ 190 | XX(22, CHECKOUT, CHECKOUT) \ 191 | XX(23, MERGE, MERGE) \ 192 | /* upnp */ \ 193 | XX(24, MSEARCH, M-SEARCH) \ 194 | XX(25, NOTIFY, NOTIFY) \ 195 | XX(26, SUBSCRIBE, SUBSCRIBE) \ 196 | XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ 197 | /* RFC-5789 */ \ 198 | XX(28, PATCH, PATCH) \ 199 | XX(29, PURGE, PURGE) \ 200 | /* CalDAV */ \ 201 | XX(30, MKCALENDAR, MKCALENDAR) \ 202 | /* RFC-2068, section 19.6.1.2 */ \ 203 | XX(31, LINK, LINK) \ 204 | XX(32, UNLINK, UNLINK) \ 205 | 206 | enum http_method 207 | { 208 | #define XX(num, name, string) HTTP_##name = num, 209 | HTTP_METHOD_MAP(XX) 210 | #undef XX 211 | }; 212 | 213 | 214 | enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; 215 | 216 | 217 | /* Flag values for http_parser.flags field */ 218 | enum flags 219 | { F_CHUNKED = 1 << 0 220 | , F_CONNECTION_KEEP_ALIVE = 1 << 1 221 | , F_CONNECTION_CLOSE = 1 << 2 222 | , F_CONNECTION_UPGRADE = 1 << 3 223 | , F_TRAILING = 1 << 4 224 | , F_UPGRADE = 1 << 5 225 | , F_SKIPBODY = 1 << 6 226 | , F_CONTENTLENGTH = 1 << 7 227 | }; 228 | 229 | 230 | /* Map for errno-related constants 231 | * 232 | * The provided argument should be a macro that takes 2 arguments. 233 | */ 234 | #define HTTP_ERRNO_MAP(XX) \ 235 | /* No error */ \ 236 | XX(OK, "success") \ 237 | \ 238 | /* Callback-related errors */ \ 239 | XX(CB_message_begin, "the on_message_begin callback failed") \ 240 | XX(CB_url, "the on_url callback failed") \ 241 | XX(CB_header_field, "the on_header_field callback failed") \ 242 | XX(CB_header_value, "the on_header_value callback failed") \ 243 | XX(CB_headers_complete, "the on_headers_complete callback failed") \ 244 | XX(CB_body, "the on_body callback failed") \ 245 | XX(CB_message_complete, "the on_message_complete callback failed") \ 246 | XX(CB_status, "the on_status callback failed") \ 247 | XX(CB_chunk_header, "the on_chunk_header callback failed") \ 248 | XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ 249 | \ 250 | /* Parsing-related errors */ \ 251 | XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ 252 | XX(HEADER_OVERFLOW, \ 253 | "too many header bytes seen; overflow detected") \ 254 | XX(CLOSED_CONNECTION, \ 255 | "data received after completed connection: close message") \ 256 | XX(INVALID_VERSION, "invalid HTTP version") \ 257 | XX(INVALID_STATUS, "invalid HTTP status code") \ 258 | XX(INVALID_METHOD, "invalid HTTP method") \ 259 | XX(INVALID_URL, "invalid URL") \ 260 | XX(INVALID_HOST, "invalid host") \ 261 | XX(INVALID_PORT, "invalid port") \ 262 | XX(INVALID_PATH, "invalid path") \ 263 | XX(INVALID_QUERY_STRING, "invalid query string") \ 264 | XX(INVALID_FRAGMENT, "invalid fragment") \ 265 | XX(LF_EXPECTED, "LF character expected") \ 266 | XX(INVALID_HEADER_TOKEN, "invalid character in header") \ 267 | XX(INVALID_CONTENT_LENGTH, \ 268 | "invalid character in content-length header") \ 269 | XX(UNEXPECTED_CONTENT_LENGTH, \ 270 | "unexpected content-length header") \ 271 | XX(INVALID_CHUNK_SIZE, \ 272 | "invalid character in chunk size header") \ 273 | XX(INVALID_CONSTANT, "invalid constant string") \ 274 | XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ 275 | XX(STRICT, "strict mode assertion failed") \ 276 | XX(PAUSED, "parser is paused") \ 277 | XX(UNKNOWN, "an unknown error occurred") 278 | 279 | 280 | /* Define HPE_* values for each errno value above */ 281 | #define HTTP_ERRNO_GEN(n, s) HPE_##n, 282 | enum http_errno { 283 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 284 | }; 285 | #undef HTTP_ERRNO_GEN 286 | 287 | 288 | /* Get an http_errno value from an http_parser */ 289 | #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) 290 | 291 | 292 | struct http_parser { 293 | /** PRIVATE **/ 294 | unsigned int type : 2; /* enum http_parser_type */ 295 | unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ 296 | unsigned int state : 7; /* enum state from http_parser.c */ 297 | unsigned int header_state : 7; /* enum header_state from http_parser.c */ 298 | unsigned int index : 7; /* index into current matcher */ 299 | unsigned int lenient_http_headers : 1; 300 | 301 | uint32_t nread; /* # bytes read in various scenarios */ 302 | uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ 303 | 304 | /** READ-ONLY **/ 305 | unsigned short http_major; 306 | unsigned short http_minor; 307 | unsigned int status_code : 16; /* responses only */ 308 | unsigned int method : 8; /* requests only */ 309 | unsigned int http_errno : 7; 310 | 311 | /* 1 = Upgrade header was present and the parser has exited because of that. 312 | * 0 = No upgrade header present. 313 | * Should be checked when http_parser_execute() returns in addition to 314 | * error checking. 315 | */ 316 | unsigned int upgrade : 1; 317 | 318 | /** PUBLIC **/ 319 | void *data; /* A pointer to get hook to the "connection" or "socket" object */ 320 | }; 321 | 322 | 323 | struct http_parser_settings { 324 | http_cb on_message_begin; 325 | http_data_cb on_url; 326 | http_data_cb on_status; 327 | http_data_cb on_header_field; 328 | http_data_cb on_header_value; 329 | http_cb on_headers_complete; 330 | http_data_cb on_body; 331 | http_cb on_message_complete; 332 | /* When on_chunk_header is called, the current chunk length is stored 333 | * in parser->content_length. 334 | */ 335 | http_cb on_chunk_header; 336 | http_cb on_chunk_complete; 337 | }; 338 | 339 | 340 | enum http_parser_url_fields 341 | { UF_SCHEMA = 0 342 | , UF_HOST = 1 343 | , UF_PORT = 2 344 | , UF_PATH = 3 345 | , UF_QUERY = 4 346 | , UF_FRAGMENT = 5 347 | , UF_USERINFO = 6 348 | , UF_MAX = 7 349 | }; 350 | 351 | 352 | /* Result structure for http_parser_parse_url(). 353 | * 354 | * Callers should index into field_data[] with UF_* values iff field_set 355 | * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and 356 | * because we probably have padding left over), we convert any port to 357 | * a uint16_t. 358 | */ 359 | struct http_parser_url { 360 | uint16_t field_set; /* Bitmask of (1 << UF_*) values */ 361 | uint16_t port; /* Converted UF_PORT string */ 362 | 363 | struct { 364 | uint16_t off; /* Offset into buffer in which field starts */ 365 | uint16_t len; /* Length of run in buffer */ 366 | } field_data[UF_MAX]; 367 | }; 368 | 369 | 370 | /* Returns the library version. Bits 16-23 contain the major version number, 371 | * bits 8-15 the minor version number and bits 0-7 the patch level. 372 | * Usage example: 373 | * 374 | * unsigned long version = http_parser_version(); 375 | * unsigned major = (version >> 16) & 255; 376 | * unsigned minor = (version >> 8) & 255; 377 | * unsigned patch = version & 255; 378 | * printf("http_parser v%u.%u.%u\n", major, minor, patch); 379 | */ 380 | unsigned long http_parser_version(void); 381 | 382 | void http_parser_init(http_parser *parser, enum http_parser_type type); 383 | 384 | 385 | /* Initialize http_parser_settings members to 0 386 | */ 387 | void http_parser_settings_init(http_parser_settings *settings); 388 | 389 | 390 | /* Executes the parser. Returns number of parsed bytes. Sets 391 | * `parser->http_errno` on error. */ 392 | size_t http_parser_execute(http_parser *parser, 393 | const http_parser_settings *settings, 394 | const char *data, 395 | size_t len); 396 | 397 | 398 | /* If http_should_keep_alive() in the on_headers_complete or 399 | * on_message_complete callback returns 0, then this should be 400 | * the last message on the connection. 401 | * If you are the server, respond with the "Connection: close" header. 402 | * If you are the client, close the connection. 403 | */ 404 | int http_should_keep_alive(const http_parser *parser); 405 | 406 | /* Returns a string version of the HTTP method. */ 407 | const char *http_method_str(enum http_method m); 408 | 409 | /* Return a string name of the given error */ 410 | const char *http_errno_name(enum http_errno err); 411 | 412 | /* Return a string description of the given error */ 413 | const char *http_errno_description(enum http_errno err); 414 | 415 | /* Initialize all http_parser_url members to 0 */ 416 | void http_parser_url_init(struct http_parser_url *u); 417 | 418 | /* Parse a URL; return nonzero on failure */ 419 | int http_parser_parse_url(const char *buf, size_t buflen, 420 | int is_connect, 421 | struct http_parser_url *u); 422 | 423 | /* Pause or un-pause the parser; a nonzero value pauses */ 424 | void http_parser_pause(http_parser *parser, int paused); 425 | 426 | /* Checks if this is the final chunk of the body. */ 427 | int http_body_is_final(const http_parser *parser); 428 | 429 | #ifdef __cplusplus 430 | } 431 | #endif 432 | #endif 433 | -------------------------------------------------------------------------------- /src/ans_module.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * BSD LICENSE 3 | * 4 | * Copyright(c) 2015-2017 Ansyun . All rights reserved. 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright 12 | * notice, 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 15 | * the documentation and/or other materials provided with the 16 | * distribution. 17 | * * Neither the name of Ansyun nor the names of its 18 | * contributors may be used to endorse or promote products derived 19 | * from this software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | * 33 | * Author: JiaKai (jiakai1000@gmail.com) and Bluestar (anssupport@163.com) 34 | */ 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #include 49 | #include 50 | #include 51 | 52 | #ifndef __linux__ 53 | #ifdef __FreeBSD__ 54 | #include 55 | #else 56 | #include 57 | #endif 58 | #endif 59 | 60 | #include 61 | #include "anssock_intf.h" 62 | #include "ans_errno.h" 63 | 64 | #define _GNU_SOURCE 65 | #define __USE_GNU 66 | 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | 73 | 74 | /* 75 | * ANS socket fd large than linux "ulimit -n " value 76 | * 77 | */ 78 | #define ANS_FD_BASE 2000 79 | 80 | /* 1: redis socket will go through ANS stack, 0: go through linux stack */ 81 | int ans_sock_enable = 1; 82 | 83 | int ansfd_debug_flag = 0; 84 | 85 | #define ANS_FD_DEBUG( fmt, args...) \ 86 | do { \ 87 | if(ansfd_debug_flag == 1) \ 88 | printf(fmt , ## args); \ 89 | } while (0) 90 | 91 | static int inited = 0; 92 | 93 | static int (*real_socket)(int, int, int); 94 | static int (*real_socketpair)(int, int, int, int *); 95 | static int (*real_bind)(int, const struct sockaddr*, socklen_t); 96 | static int (*real_connect)(int, const struct sockaddr*, socklen_t); 97 | static int (*real_listen)(int, int); 98 | static int (*real_accept)(int, struct sockaddr *, socklen_t *); 99 | static int (*real_accept4)(int, struct sockaddr *, socklen_t *, int); 100 | static ssize_t (*real_recv)(int, void *, size_t, int); 101 | static ssize_t (*real_send)(int, const void *, size_t, int); 102 | static int (*real_shutdown)(int, int); 103 | static int (*real_close)(int); 104 | 105 | static ssize_t (*real_writev)(int, const struct iovec *, int); 106 | static ssize_t (*real_write)(int, const void *, size_t ); 107 | static ssize_t (*real_read)(int, void *, size_t ); 108 | static ssize_t (*real_readv)(int, const struct iovec *, int); 109 | 110 | static int (*real_getsockopt)(int, int, int, const void *, socklen_t*); 111 | static int (*real_setsockopt)(int, int, int, const void *, socklen_t); 112 | static int (*real_getpeername)(int , struct sockaddr *, socklen_t *); 113 | static int (*real_getsockname)(int , struct sockaddr *, socklen_t *); 114 | 115 | static int (*real_ioctl)(int, int, void *); 116 | 117 | static int (*real_epoll_create)(int); 118 | static int (*real_epoll_ctl)(int, int, int, struct epoll_event *); 119 | static int (*real_epoll_wait)(int, struct epoll_event *, int, int); 120 | 121 | /** 122 | * @param 123 | * 124 | * @return 125 | * 126 | */ 127 | void ans_mod_init(char *file_prefix) 128 | { 129 | int rc; 130 | 131 | #define INIT_FUNCTION(func) \ 132 | real_##func = dlsym(RTLD_NEXT, #func); \ 133 | assert(real_##func) 134 | 135 | INIT_FUNCTION(socket); 136 | INIT_FUNCTION(socketpair); 137 | INIT_FUNCTION(bind); 138 | INIT_FUNCTION(connect); 139 | INIT_FUNCTION(close); 140 | INIT_FUNCTION(listen); 141 | INIT_FUNCTION(accept); 142 | INIT_FUNCTION(accept4); 143 | INIT_FUNCTION(recv); 144 | INIT_FUNCTION(send); 145 | INIT_FUNCTION(shutdown); 146 | INIT_FUNCTION(writev); 147 | INIT_FUNCTION(write); 148 | INIT_FUNCTION(read); 149 | INIT_FUNCTION(readv); 150 | INIT_FUNCTION(getsockopt); 151 | INIT_FUNCTION(setsockopt); 152 | INIT_FUNCTION(getpeername); 153 | INIT_FUNCTION(getsockname); 154 | INIT_FUNCTION(ioctl); 155 | 156 | INIT_FUNCTION(epoll_create); 157 | INIT_FUNCTION(epoll_ctl); 158 | INIT_FUNCTION(epoll_wait); 159 | 160 | #undef INIT_FUNCTION 161 | 162 | if(ans_sock_enable != 1) 163 | { 164 | printf("ans socket is disable \n"); 165 | return; 166 | } 167 | 168 | rc = anssock_init(file_prefix); 169 | if(rc != ANS_EOK) 170 | { 171 | printf("anssock init failed \n"); 172 | } 173 | assert(0 == rc); 174 | 175 | inited = 1; 176 | 177 | return; 178 | } 179 | 180 | /** 181 | * @param 182 | * 183 | * @return 184 | * 185 | */ 186 | int socket(int domain, int type, int protocol) 187 | { 188 | int rc; 189 | 190 | ANS_FD_DEBUG("socket create start , domain %d, type %d \n", domain, type); 191 | 192 | if ((inited == 0) || (AF_INET != domain) || (SOCK_STREAM != type && SOCK_DGRAM != type)) 193 | { 194 | rc = real_socket(domain, type, protocol); 195 | ANS_FD_DEBUG("linux socket fd %d \n", rc); 196 | 197 | return rc; 198 | } 199 | 200 | assert(inited); 201 | rc = anssock_socket(domain, type, protocol); 202 | 203 | if(rc > 0) 204 | rc += ANS_FD_BASE; 205 | 206 | ANS_FD_DEBUG("ans socket fd %d \n", rc); 207 | return rc; 208 | } 209 | 210 | /** 211 | * @param 212 | * 213 | * @return 214 | * 215 | */ 216 | int socketpair(int domain, int type, int protocol, int sv[2]) 217 | { 218 | if (inited) 219 | { 220 | return -1; 221 | } 222 | else 223 | { 224 | return real_socketpair(domain, type, protocol, sv); 225 | } 226 | } 227 | 228 | /** 229 | * @param 230 | * 231 | * @return 232 | * 233 | */ 234 | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) 235 | { 236 | struct sockaddr_in *in_addr; 237 | in_addr = (struct sockaddr_in *)addr; 238 | 239 | ANS_FD_DEBUG("bind ip: %x , port %d, family:%d \n", in_addr->sin_addr.s_addr, ntohs(in_addr->sin_port), in_addr->sin_family); 240 | 241 | if(inited && sockfd > ANS_FD_BASE) 242 | { 243 | sockfd -= ANS_FD_BASE; 244 | return anssock_bind(sockfd, addr, addrlen); 245 | } 246 | else 247 | { 248 | return real_bind(sockfd, addr, addrlen); 249 | } 250 | } 251 | 252 | /** 253 | * @param 254 | * 255 | * @return 256 | * 257 | */ 258 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) 259 | { 260 | 261 | ANS_FD_DEBUG("fd(%d) start to connect \n", sockfd); 262 | 263 | if(inited && sockfd > ANS_FD_BASE) 264 | { 265 | sockfd -= ANS_FD_BASE; 266 | return anssock_connect(sockfd, addr, addrlen); 267 | } 268 | else 269 | { 270 | return real_connect(sockfd, addr, addrlen); 271 | } 272 | 273 | } 274 | 275 | /** 276 | * @param 277 | * 278 | * @return 279 | * 280 | */ 281 | ssize_t send (int sockfd, const void *buf, size_t len, int flags) 282 | { 283 | ssize_t n; 284 | 285 | ANS_FD_DEBUG("send data fd %d , len %lu \n", sockfd, len); 286 | 287 | if(inited && sockfd > ANS_FD_BASE) 288 | { 289 | sockfd -= ANS_FD_BASE; 290 | ANS_FD_DEBUG("ans send data fd %d , len %lu \n", sockfd, len); 291 | 292 | n = anssock_send(sockfd, buf, len, flags); 293 | 294 | ANS_FD_DEBUG("ans send: fd %d , len %lu, return value:%ld, errno:%d, strerror = %s \n", sockfd, len, n, errno, strerror(errno)); 295 | 296 | return n; 297 | 298 | } 299 | else 300 | { 301 | ANS_FD_DEBUG("linux send data fd %d , len %lu \n", sockfd, len); 302 | 303 | return real_send(sockfd, buf, len, flags); 304 | } 305 | } 306 | 307 | /** 308 | * @param 309 | * 310 | * @return 311 | * 312 | */ 313 | ssize_t write(int fd, const void *buf, size_t count) 314 | { 315 | ssize_t n; 316 | 317 | // ANS_FD_DEBUG("write data fd %d , len %lu \n", fd, count); 318 | 319 | if(inited && fd > ANS_FD_BASE) 320 | { 321 | fd -= ANS_FD_BASE; 322 | 323 | n = anssock_write(fd, buf, count); 324 | 325 | ANS_FD_DEBUG("ans write: fd %d , len %lu, return value:%ld, errno:%d, strerror = %s \n", fd, count, n, errno, strerror(errno)); 326 | 327 | return n; 328 | 329 | } 330 | else 331 | { 332 | 333 | n = real_write(fd, buf, count); 334 | 335 | ANS_FD_DEBUG("linux write: fd %d , len %lu, return value:%ld, errno:%d, strerror = %s \n", fd, count, n, errno, strerror(errno)); 336 | 337 | return n; 338 | } 339 | } 340 | 341 | /** 342 | * @param 343 | * 344 | * @return 345 | * 346 | */ 347 | ssize_t recv(int sockfd, void *buf, size_t len, int flags) 348 | { 349 | ssize_t rc; 350 | if(inited && sockfd > ANS_FD_BASE) 351 | { 352 | sockfd -= ANS_FD_BASE; 353 | 354 | rc = anssock_recv(sockfd, buf, len, flags); 355 | if (-1 == rc && ANS_EAGAIN == errno) 356 | { 357 | errno = EAGAIN; 358 | } 359 | 360 | ANS_FD_DEBUG("ans fd %d recv data len %ld \n", sockfd, rc); 361 | 362 | return rc; 363 | } 364 | else 365 | { 366 | rc = real_recv(sockfd, buf, len, flags); 367 | 368 | ANS_FD_DEBUG("linux fd %d recv data len %ld \n", sockfd, rc); 369 | 370 | return rc; 371 | } 372 | } 373 | 374 | /** 375 | * @param 376 | * 377 | * @return 378 | * 379 | */ 380 | ssize_t read(int fd, void *buf, size_t count) 381 | { 382 | ssize_t rc; 383 | if(inited && fd > ANS_FD_BASE) 384 | { 385 | fd -= ANS_FD_BASE; 386 | 387 | rc = anssock_read(fd, buf, count); 388 | 389 | ANS_FD_DEBUG("ans fd %d read data len %ld, %d \n", fd, rc, errno); 390 | 391 | return rc; 392 | } 393 | else 394 | { 395 | rc =real_read(fd, buf, count); 396 | // ANS_FD_DEBUG("linux fd %d read data len %ld \n", fd, rc); 397 | 398 | return rc; 399 | } 400 | } 401 | 402 | /** 403 | * @param 404 | * 405 | * @return 406 | * 407 | */ 408 | ssize_t sendto(__attribute__((unused)) int sockfd, __attribute__((unused))const void *buf, __attribute__((unused))size_t len, __attribute__((unused))int flags, 409 | __attribute__((unused))const struct sockaddr *dest_addr, __attribute__((unused))socklen_t addrlen) 410 | { 411 | return -1; 412 | } 413 | 414 | /** 415 | * @param 416 | * 417 | * @return 418 | * 419 | */ 420 | ssize_t recvfrom(__attribute__((unused))int sockfd, __attribute__((unused))void *buf, __attribute__((unused))size_t len, __attribute__((unused))int flags, 421 | __attribute__((unused))struct sockaddr *src_addr, __attribute__((unused))socklen_t *addrlen) 422 | { 423 | return -1; 424 | } 425 | 426 | /** 427 | * @param 428 | * 429 | * @return 430 | * 431 | */ 432 | int getsockopt(int sockfd, int level ,int optname, void *optval, socklen_t *optlen) 433 | { 434 | if(inited && sockfd > ANS_FD_BASE) 435 | { 436 | return -2; 437 | } 438 | else 439 | { 440 | return real_getsockopt(sockfd, level, optname, optval, optlen); 441 | } 442 | } 443 | 444 | /** 445 | * @param 446 | * 447 | * @return 448 | * 449 | */ 450 | int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) 451 | { 452 | if(inited && sockfd > ANS_FD_BASE) 453 | { 454 | sockfd -= ANS_FD_BASE; 455 | 456 | return anssock_setsockopt(sockfd, level, optname, optval, optlen); 457 | } 458 | else 459 | { 460 | return real_setsockopt(sockfd, level, optname, optval, optlen); 461 | } 462 | } 463 | 464 | /** 465 | * @param 466 | * 467 | * @return 468 | * 469 | */ 470 | int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen) 471 | { 472 | if(inited && sockfd > ANS_FD_BASE) 473 | { 474 | sockfd -= ANS_FD_BASE; 475 | 476 | return anssock_getpeername(sockfd, addr, addrlen); 477 | } 478 | else 479 | { 480 | return real_getpeername(sockfd, addr, addrlen); 481 | } 482 | } 483 | 484 | /** 485 | * @param 486 | * 487 | * @return 488 | * 489 | */ 490 | int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) 491 | { 492 | if(inited && sockfd > ANS_FD_BASE) 493 | { 494 | sockfd -= ANS_FD_BASE; 495 | 496 | return anssock_getsockname(sockfd, addr, addrlen); 497 | } 498 | else 499 | { 500 | return real_getsockname(sockfd, addr, addrlen); 501 | } 502 | } 503 | 504 | 505 | /** 506 | * @param 507 | * 508 | * @return 509 | * 510 | */ 511 | int listen(int sockfd, int backlog) 512 | { 513 | if(inited && sockfd > ANS_FD_BASE) 514 | { 515 | sockfd -= ANS_FD_BASE; 516 | 517 | ANS_FD_DEBUG("ans listen fd %d, pid %d \n", sockfd, getpid()); 518 | 519 | return anssock_listen(sockfd, backlog); 520 | } 521 | else 522 | { 523 | ANS_FD_DEBUG("linux listen fd %d , pid %d \n", sockfd, getpid()); 524 | 525 | return real_listen(sockfd, backlog); 526 | } 527 | } 528 | 529 | /** 530 | * @param 531 | * 532 | * @return 533 | * 534 | */ 535 | int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) 536 | { 537 | int rc; 538 | 539 | if(inited && sockfd > ANS_FD_BASE) 540 | { 541 | sockfd -= ANS_FD_BASE; 542 | 543 | rc = anssock_accept(sockfd, addr, addrlen); 544 | addr->sa_family = AF_INET; 545 | 546 | ANS_FD_DEBUG("ans accept fd %d \n", rc); 547 | if(rc > 0 ) 548 | rc += ANS_FD_BASE; 549 | } 550 | else 551 | { 552 | rc = real_accept(sockfd, addr, addrlen); 553 | ANS_FD_DEBUG("linux accept fd %d \n", rc); 554 | 555 | } 556 | return rc; 557 | } 558 | 559 | /** 560 | * @param 561 | * 562 | * @return 563 | * 564 | */ 565 | int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) 566 | { 567 | int rc; 568 | 569 | if(inited && sockfd > ANS_FD_BASE) 570 | { 571 | sockfd -= ANS_FD_BASE; 572 | 573 | rc = anssock_accept(sockfd, addr, addrlen); 574 | addr->sa_family = AF_INET; 575 | 576 | ANS_FD_DEBUG("ans accep4t fd %d, errno %d \n", rc, errno); 577 | 578 | if(rc > 0 ) 579 | rc += ANS_FD_BASE; 580 | 581 | } 582 | else 583 | { 584 | rc = real_accept4(sockfd, addr, addrlen, flags); 585 | ANS_FD_DEBUG("linux accept4 fd %d, errno %d \n", rc, errno); 586 | } 587 | return rc; 588 | } 589 | 590 | /** 591 | * @param 592 | * 593 | * @return 594 | * 595 | */ 596 | int shutdown (int fd, int how) 597 | { 598 | ANS_FD_DEBUG("ans shutdown fd %d, how %d, pid %d \n", fd, how, getpid()); 599 | 600 | if(inited && fd > ANS_FD_BASE) 601 | { 602 | fd -= ANS_FD_BASE; 603 | 604 | return anssock_shutdown(fd, how);; 605 | } 606 | else 607 | { 608 | return real_shutdown(fd, how); 609 | } 610 | } 611 | 612 | /** 613 | * @param 614 | * 615 | * @return 616 | * 617 | */ 618 | int close(int fd) 619 | { 620 | if(inited && fd > ANS_FD_BASE) 621 | { 622 | fd -= ANS_FD_BASE; 623 | 624 | ANS_FD_DEBUG("ans close fd %d, pid %d \n", fd, getpid()); 625 | 626 | return anssock_close(fd); 627 | } 628 | else 629 | { 630 | // ANS_FD_DEBUG("linux close fd %d \n", fd); 631 | 632 | return real_close(fd); 633 | } 634 | } 635 | 636 | /** 637 | * @param 638 | * 639 | * @return 640 | * 641 | */ 642 | int epoll_create (int size) 643 | { 644 | int rc; 645 | 646 | ANS_FD_DEBUG("epoll create start, EPOLL_CTL_ADD %d ,EPOLL_CTL_DEL %d, EPOLLOUT 0x%x, EPOLLIN:0x%x \n", EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLLOUT, EPOLLIN); 647 | 648 | if (inited) 649 | { 650 | rc = anssock_epoll_create(size); 651 | if(rc > 0) 652 | rc += ANS_FD_BASE; 653 | 654 | ANS_FD_DEBUG("ans epoll fd %d \n", rc); 655 | } 656 | else 657 | { 658 | rc = real_epoll_create(size); 659 | ANS_FD_DEBUG("linux epoll fd %d \n", rc); 660 | } 661 | return rc; 662 | } 663 | 664 | /** 665 | * @param 666 | * 667 | * @return 668 | * 669 | */ 670 | int epoll_create1 (__attribute__((unused))int __flags) 671 | { 672 | return -1; 673 | } 674 | 675 | /** 676 | * @param 677 | * 678 | * @return 679 | * 680 | */ 681 | int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 682 | { 683 | int rc; 684 | 685 | ANS_FD_DEBUG("epoll ctl start, epfd %d ,op %d, fd %d, event:0x%x \n", epfd, op, fd, event->events); 686 | 687 | if (inited && epfd > ANS_FD_BASE) 688 | { 689 | if(fd <= ANS_FD_BASE) 690 | { 691 | printf("skip linux fd %d \n", fd); 692 | return 0; 693 | } 694 | epfd -= ANS_FD_BASE; 695 | fd -= ANS_FD_BASE; 696 | 697 | rc = anssock_epoll_ctl(epfd, op, fd, event); 698 | } 699 | else 700 | { 701 | if(inited && fd > ANS_FD_BASE) 702 | { 703 | printf("skip ans fd %d \n", fd); 704 | return 0; 705 | } 706 | 707 | rc = real_epoll_ctl(epfd, op, fd, event); 708 | } 709 | return rc; 710 | } 711 | 712 | int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) 713 | { 714 | int rc; 715 | 716 | if (inited && epfd > ANS_FD_BASE) 717 | { 718 | epfd -= ANS_FD_BASE; 719 | 720 | // ANS_FD_DEBUG("ans epoll_wait: fd %d maxevents %d , timeout %d \n", epfd, maxevents, timeout); 721 | 722 | rc = anssock_epoll_wait(epfd, events, maxevents, timeout); 723 | } 724 | else 725 | { 726 | // ANS_FD_DEBUG("linux epoll_wait: fd %d maxevents %d , timeout %d \n", epfd, maxevents, timeout); 727 | 728 | rc = real_epoll_wait(epfd, events, maxevents, timeout); 729 | } 730 | return rc; 731 | } 732 | 733 | /** 734 | * @param 735 | * 736 | * @return 737 | * 738 | */ 739 | int epoll_pwait (__attribute__((unused))int __epfd, __attribute__((unused))struct epoll_event *__events, __attribute__((unused))int __maxevents, 740 | __attribute__((unused))int __timeout, __attribute__((unused))const __sigset_t *__ss) 741 | { 742 | return -1; 743 | } 744 | 745 | 746 | /** 747 | * @param 748 | * 749 | * @return 750 | * 751 | */ 752 | int ioctl(int fd, int request, void *p) 753 | { 754 | if (inited && fd > ANS_FD_BASE) 755 | { 756 | fd -= ANS_FD_BASE; 757 | 758 | //return anssock_ioctl(fd, request, p); 759 | return 0; 760 | } 761 | else 762 | { 763 | return real_ioctl(fd, request, p); 764 | } 765 | } 766 | 767 | 768 | /** 769 | * @param 770 | * 771 | * @return 772 | * 773 | */ 774 | ssize_t writev(int fd, const struct iovec *iov, int iovcnt) 775 | { 776 | ssize_t rc; 777 | 778 | if (inited && fd > ANS_FD_BASE) 779 | { 780 | fd -= ANS_FD_BASE; 781 | 782 | ANS_FD_DEBUG("ans writev data fd %d , iovcnt %d \n", fd, iovcnt); 783 | rc = anssock_writev(fd, iov, iovcnt); 784 | } 785 | else 786 | { 787 | // ANS_FD_DEBUG("linux writev data fd %d , len %d \n", fd, iovcnt); 788 | 789 | rc = real_writev(fd, iov, iovcnt); 790 | } 791 | return rc; 792 | } 793 | 794 | /** 795 | * @param 796 | * 797 | * @return 798 | * 799 | */ 800 | ssize_t readv(int fd, const struct iovec *iov, int iovcnt) 801 | { 802 | ssize_t rc; 803 | 804 | if (inited && fd > ANS_FD_BASE) 805 | { 806 | fd -= ANS_FD_BASE; 807 | 808 | ANS_FD_DEBUG("ans fd %d readv with iovcnt %d \n", fd, iovcnt); 809 | rc =anssock_readv(fd, iov, iovcnt); 810 | return rc; 811 | } 812 | else 813 | { 814 | rc =real_readv(fd, iov, iovcnt); 815 | 816 | return rc; 817 | } 818 | 819 | } 820 | -------------------------------------------------------------------------------- /src/wrk.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 - Will Glozer. All rights reserved. 2 | #include 3 | #include 4 | #include "wrk.h" 5 | #include "script.h" 6 | #include "main.h" 7 | #include "anssock_intf.h" 8 | #include "ans_errno.h" 9 | 10 | void ans_mod_init(char *file_prefix); 11 | 12 | static struct config { 13 | uint64_t connections; 14 | uint64_t duration; 15 | uint64_t threads; 16 | uint64_t timeout; 17 | uint64_t pipeline; 18 | bool delay; 19 | bool dynamic; 20 | bool latency; 21 | char *host; 22 | char *script; 23 | char *iprange; 24 | uint32_t ip_start; 25 | uint32_t ip_end; 26 | uint64_t send_delay; 27 | char *file_prefix; 28 | SSL_CTX *ctx; 29 | } cfg; 30 | 31 | static struct { 32 | stats *latency; 33 | stats *requests; 34 | } statistics; 35 | 36 | static struct sock sock = { 37 | .connect = sock_connect, 38 | .close = sock_close, 39 | .read = sock_read, 40 | .write = sock_write, 41 | .readable = sock_readable 42 | }; 43 | 44 | static struct http_parser_settings parser_settings = { 45 | .on_message_complete = response_complete 46 | }; 47 | 48 | static volatile sig_atomic_t stop = 0; 49 | 50 | static void handler(int sig) { 51 | stop = 1; 52 | } 53 | 54 | static void usage() { 55 | printf("Usage: dpdk-httpperf \n" 56 | " Options: \n" 57 | " -c, --connections Connections to keep open \n" 58 | " -d, --duration Duration of test \n" 59 | " -t, --threads Number of threads to use \n" 60 | " \n" 61 | " -s, --script Load Lua script file \n" 62 | " -H, --header Add header to request \n" 63 | " --latency Print latency statistics \n" 64 | " --timeout Socket/request timeout \n" 65 | " --iprange bind IP range(10.0.0.20-10.0.0.30) \n" 66 | " -v, --version Print version details \n" 67 | " -D, --delay delay for send request \n" 68 | " \n" 69 | " Numeric arguments may include a SI unit (1k, 1M, 1G)\n" 70 | " Time arguments may include a time unit (2s, 2m, 2h)\n"); 71 | } 72 | 73 | int main(int argc, char **argv) { 74 | char *url, **headers = zmalloc(argc * sizeof(char *)); 75 | struct http_parser_url parts = {}; 76 | 77 | if (parse_args(&cfg, &url, &parts, headers, argc, argv)) { 78 | usage(); 79 | exit(1); 80 | } 81 | 82 | char *schema = copy_url_part(url, &parts, UF_SCHEMA); 83 | char *host = copy_url_part(url, &parts, UF_HOST); 84 | char *port = copy_url_part(url, &parts, UF_PORT); 85 | char *service = port ? port : schema; 86 | 87 | if (!strncmp("https", schema, 5)) { 88 | if ((cfg.ctx = ssl_init()) == NULL) { 89 | fprintf(stderr, "unable to initialize SSL\n"); 90 | ERR_print_errors_fp(stderr); 91 | exit(1); 92 | } 93 | sock.connect = ssl_connect; 94 | sock.close = ssl_close; 95 | sock.read = ssl_read; 96 | sock.write = ssl_write; 97 | sock.readable = ssl_readable; 98 | } 99 | 100 | printf("start init anssock \n"); 101 | /* init ans socket */ 102 | ans_mod_init(cfg.file_prefix); 103 | 104 | signal(SIGPIPE, SIG_IGN); 105 | signal(SIGINT, SIG_IGN); 106 | 107 | statistics.latency = stats_alloc(cfg.timeout * 1000); 108 | statistics.requests = stats_alloc(MAX_THREAD_RATE_S); 109 | thread *threads = zcalloc(cfg.threads * sizeof(thread)); 110 | 111 | lua_State *L = script_create(cfg.script, url, headers); 112 | if (!script_resolve(L, host, service)) { 113 | char *msg = strerror(errno); 114 | fprintf(stderr, "unable to connect to %s:%s %s\n", host, service, msg); 115 | exit(1); 116 | } 117 | 118 | cfg.host = host; 119 | 120 | printf("thread number %ld \n", cfg.threads); 121 | 122 | int ip_nb = cfg.ip_end - cfg.ip_start; 123 | int ip_range = 0; 124 | if(ip_nb) 125 | { 126 | printf("local IP number %d \n", ip_nb); 127 | if(ip_nb < cfg.threads) 128 | { 129 | printf("IP number shall large than threads number \n"); 130 | exit(1); 131 | } 132 | ip_range = ip_nb / cfg.threads; 133 | } 134 | 135 | 136 | for (uint64_t i = 0; i < cfg.threads; i++) { 137 | thread *t = &threads[i]; 138 | /* ans fd may very large, shall be same as ans fd config, set 200000 as default value */ 139 | t->loop = aeCreateEventLoop(200000 + 10 + cfg.connections * 3); 140 | t->connections = cfg.connections / cfg.threads; 141 | if(ip_range) 142 | { 143 | t->ip_start = cfg.ip_start + i * ip_range; 144 | t->ip_end = t->ip_start + ip_range - 1; 145 | } 146 | else 147 | { 148 | t->ip_start = 0; 149 | t->ip_end = 0; 150 | } 151 | 152 | t->L = script_create(cfg.script, url, headers); 153 | script_init(L, t, argc - optind, &argv[optind]); 154 | 155 | if (i == 0) { 156 | cfg.pipeline = script_verify_request(t->L); 157 | cfg.dynamic = !script_is_static(t->L); 158 | cfg.delay = script_has_delay(t->L); 159 | if (script_want_response(t->L)) { 160 | parser_settings.on_header_field = header_field; 161 | parser_settings.on_header_value = header_value; 162 | parser_settings.on_body = response_body; 163 | } 164 | } 165 | 166 | if (!t->loop || pthread_create(&t->thread, NULL, &thread_main, t)) { 167 | char *msg = strerror(errno); 168 | fprintf(stderr, "unable to create thread %"PRIu64": %s\n", i, msg); 169 | exit(2); 170 | } 171 | } 172 | 173 | struct sigaction sa = { 174 | .sa_handler = handler, 175 | .sa_flags = 0, 176 | }; 177 | sigfillset(&sa.sa_mask); 178 | sigaction(SIGINT, &sa, NULL); 179 | 180 | char *time = format_time_s(cfg.duration); 181 | printf("Running %s test @ %s\n", time, url); 182 | printf(" %"PRIu64" threads and %"PRIu64" connections\n", cfg.threads, cfg.connections); 183 | 184 | uint64_t start = time_us(); 185 | uint64_t complete = 0; 186 | uint64_t bytes = 0; 187 | errors errors = { 0 }; 188 | 189 | sleep(cfg.duration); 190 | stop = 1; 191 | 192 | for (uint64_t i = 0; i < cfg.threads; i++) { 193 | thread *t = &threads[i]; 194 | pthread_join(t->thread, NULL); 195 | 196 | complete += t->complete; 197 | bytes += t->bytes; 198 | 199 | errors.connect += t->errors.connect; 200 | errors.read += t->errors.read; 201 | errors.write += t->errors.write; 202 | errors.timeout += t->errors.timeout; 203 | errors.status += t->errors.status; 204 | } 205 | 206 | uint64_t runtime_us = time_us() - start; 207 | long double runtime_s = runtime_us / 1000000.0; 208 | long double req_per_s = complete / runtime_s; 209 | long double bytes_per_s = bytes / runtime_s; 210 | 211 | if (complete / cfg.connections > 0) { 212 | int64_t interval = runtime_us / (complete / cfg.connections); 213 | stats_correct(statistics.latency, interval); 214 | } 215 | 216 | print_stats_header(); 217 | print_stats("Latency", statistics.latency, format_time_us); 218 | print_stats("Req/Sec", statistics.requests, format_metric); 219 | if (cfg.latency) print_stats_latency(statistics.latency); 220 | 221 | char *runtime_msg = format_time_us(runtime_us); 222 | 223 | printf(" %"PRIu64" requests in %s, %sB read\n", complete, runtime_msg, format_binary(bytes)); 224 | if (errors.connect || errors.read || errors.write || errors.timeout) { 225 | printf(" Socket errors: connect %d, read %d, write %d, timeout %d\n", 226 | errors.connect, errors.read, errors.write, errors.timeout); 227 | } 228 | 229 | if (errors.status) { 230 | printf(" Non-2xx or 3xx responses: %d\n", errors.status); 231 | } 232 | 233 | printf("Requests/sec: %9.2Lf\n", req_per_s); 234 | printf("Transfer/sec: %10sB\n", format_binary(bytes_per_s)); 235 | 236 | if (script_has_done(L)) { 237 | script_summary(L, runtime_us, complete, bytes); 238 | script_errors(L, &errors); 239 | script_done(L, statistics.latency, statistics.requests); 240 | } 241 | 242 | return 0; 243 | } 244 | 245 | void *thread_main(void *arg) { 246 | thread *thread = arg; 247 | 248 | char *request = NULL; 249 | size_t length = 0; 250 | uint32_t ip_range; 251 | 252 | if (!cfg.dynamic) { 253 | script_request(thread->L, &request, &length); 254 | } 255 | 256 | ip_range = thread->ip_end - thread->ip_start; 257 | printf("thread %p, ip start 0x%x, ip end 0x%x \n", thread, thread->ip_start, thread->ip_end); 258 | 259 | thread->cs = zcalloc(thread->connections * sizeof(connection)); 260 | connection *c = thread->cs; 261 | 262 | for (uint64_t i = 0; i < thread->connections; i++, c++) { 263 | c->thread = thread; 264 | c->ssl = cfg.ctx ? SSL_new(cfg.ctx) : NULL; 265 | c->request = request; 266 | c->length = length; 267 | c->delayed = cfg.delay; 268 | 269 | if(thread->ip_start > 0) 270 | { 271 | c->local_ip = thread->ip_start + i % (ip_range + 1); 272 | } 273 | else 274 | { 275 | c->local_ip = 0; 276 | } 277 | 278 | connect_socket(thread, c); 279 | } 280 | 281 | aeEventLoop *loop = thread->loop; 282 | aeCreateTimeEvent(loop, RECORD_INTERVAL_MS, record_rate, thread, NULL); 283 | 284 | thread->start = time_us(); 285 | aeMain(loop); 286 | 287 | aeDeleteEventLoop(loop); 288 | zfree(thread->cs); 289 | 290 | return NULL; 291 | } 292 | 293 | static int connect_socket(thread *thread, connection *c) { 294 | struct addrinfo *addr = thread->addr; 295 | struct aeEventLoop *loop = thread->loop; 296 | int fd, flags; 297 | struct sockaddr_in local_addr; 298 | int opt_val = 1; 299 | 300 | memset(&local_addr,0,sizeof(local_addr)); 301 | local_addr.sin_family = AF_INET; 302 | local_addr.sin_addr.s_addr = htonl(c->local_ip); 303 | local_addr.sin_port = 0; /* stack assign local port */ 304 | 305 | fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); 306 | 307 | if(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt_val, sizeof(int)) < 0) 308 | { 309 | printf("set socket SO_REUSEPORT option failed \n"); 310 | } 311 | 312 | if(c->local_ip) 313 | { 314 | if (bind(fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in)) < 0) 315 | { 316 | printf("bind error, local ip 0x%x \n", c->local_ip); 317 | goto error; 318 | } 319 | } 320 | 321 | flags = fcntl(fd, F_GETFL, 0); 322 | fcntl(fd, F_SETFL, flags | O_NONBLOCK); 323 | 324 | if (connect(fd, addr->ai_addr, addr->ai_addrlen) == -1) { 325 | if (errno != EINPROGRESS) goto error; 326 | } 327 | 328 | flags = 1; 329 | setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags)); 330 | 331 | flags = AE_READABLE | AE_WRITABLE; 332 | if (aeCreateFileEvent(loop, fd, flags, socket_connected, c) == AE_OK) { 333 | c->parser.data = c; 334 | c->fd = fd; 335 | return fd; 336 | } 337 | 338 | error: 339 | thread->errors.connect++; 340 | close(fd); 341 | return -1; 342 | } 343 | 344 | static int reconnect_socket(thread *thread, connection *c) { 345 | aeDeleteFileEvent(thread->loop, c->fd, AE_WRITABLE | AE_READABLE); 346 | sock.close(c); 347 | close(c->fd); 348 | return connect_socket(thread, c); 349 | } 350 | 351 | static int record_rate(aeEventLoop *loop, long long id, void *data) { 352 | thread *thread = data; 353 | 354 | if (thread->requests > 0) { 355 | uint64_t elapsed_ms = (time_us() - thread->start) / 1000; 356 | uint64_t requests = (thread->requests / (double) elapsed_ms) * 1000; 357 | 358 | stats_record(statistics.requests, requests); 359 | 360 | thread->requests = 0; 361 | thread->start = time_us(); 362 | } 363 | 364 | if (stop) aeStop(loop); 365 | 366 | return RECORD_INTERVAL_MS; 367 | } 368 | 369 | static int delay_request(aeEventLoop *loop, long long id, void *data) { 370 | connection *c = data; 371 | c->delayed = false; 372 | aeCreateFileEvent(loop, c->fd, AE_WRITABLE, socket_writeable, c); 373 | return AE_NOMORE; 374 | } 375 | 376 | static int header_field(http_parser *parser, const char *at, size_t len) { 377 | connection *c = parser->data; 378 | if (c->state == VALUE) { 379 | *c->headers.cursor++ = '\0'; 380 | c->state = FIELD; 381 | } 382 | buffer_append(&c->headers, at, len); 383 | return 0; 384 | } 385 | 386 | static int header_value(http_parser *parser, const char *at, size_t len) { 387 | connection *c = parser->data; 388 | if (c->state == FIELD) { 389 | *c->headers.cursor++ = '\0'; 390 | c->state = VALUE; 391 | } 392 | buffer_append(&c->headers, at, len); 393 | return 0; 394 | } 395 | 396 | static int response_body(http_parser *parser, const char *at, size_t len) { 397 | connection *c = parser->data; 398 | buffer_append(&c->body, at, len); 399 | return 0; 400 | } 401 | 402 | static int response_complete(http_parser *parser) { 403 | connection *c = parser->data; 404 | thread *thread = c->thread; 405 | uint64_t now = time_us(); 406 | int status = parser->status_code; 407 | 408 | thread->complete++; 409 | thread->requests++; 410 | 411 | if (status > 399) { 412 | thread->errors.status++; 413 | } 414 | 415 | if (c->headers.buffer) { 416 | *c->headers.cursor++ = '\0'; 417 | script_response(thread->L, status, &c->headers, &c->body); 418 | c->state = FIELD; 419 | } 420 | 421 | if (--c->pending == 0) { 422 | if (!stats_record(statistics.latency, now - c->start)) { 423 | thread->errors.timeout++; 424 | } 425 | c->delayed = cfg.delay; 426 | aeCreateFileEvent(thread->loop, c->fd, AE_WRITABLE, socket_writeable, c); 427 | } 428 | 429 | if (!http_should_keep_alive(parser)) { 430 | reconnect_socket(thread, c); 431 | goto done; 432 | } 433 | 434 | http_parser_init(parser, HTTP_RESPONSE); 435 | 436 | done: 437 | return 0; 438 | } 439 | 440 | static void socket_connected(aeEventLoop *loop, int fd, void *data, int mask) { 441 | connection *c = data; 442 | 443 | switch (sock.connect(c, cfg.host)) { 444 | case OK: break; 445 | case ERROR: goto error; 446 | case RETRY: return; 447 | } 448 | 449 | http_parser_init(&c->parser, HTTP_RESPONSE); 450 | c->written = 0; 451 | 452 | aeCreateFileEvent(c->thread->loop, fd, AE_READABLE, socket_readable, c); 453 | aeCreateFileEvent(c->thread->loop, fd, AE_WRITABLE, socket_writeable, c); 454 | 455 | return; 456 | 457 | error: 458 | c->thread->errors.connect++; 459 | reconnect_socket(c->thread, c); 460 | } 461 | 462 | static void socket_writeable(aeEventLoop *loop, int fd, void *data, int mask) { 463 | connection *c = data; 464 | thread *thread = c->thread; 465 | /* 466 | if (c->delayed) { 467 | uint64_t delay = script_delay(thread->L); 468 | aeDeleteFileEvent(loop, fd, AE_WRITABLE); 469 | aeCreateTimeEvent(loop, delay, delay_request, c, NULL); 470 | return; 471 | } 472 | */ 473 | if (cfg.send_delay) { 474 | aeDeleteFileEvent(loop, fd, AE_WRITABLE); 475 | aeCreateTimeEvent(loop, cfg.send_delay, delay_request, c, NULL); 476 | return; 477 | } 478 | 479 | if (!c->written) { 480 | if (cfg.dynamic) { 481 | script_request(thread->L, &c->request, &c->length); 482 | } 483 | c->start = time_us(); 484 | c->pending = cfg.pipeline; 485 | } 486 | 487 | char *buf = c->request + c->written; 488 | size_t len = c->length - c->written; 489 | size_t n; 490 | 491 | switch (sock.write(c, buf, len, &n)) { 492 | case OK: break; 493 | case ERROR: goto error; 494 | case RETRY: return; 495 | } 496 | 497 | c->written += n; 498 | if (c->written == c->length) { 499 | c->written = 0; 500 | aeDeleteFileEvent(loop, fd, AE_WRITABLE); 501 | } 502 | 503 | return; 504 | 505 | error: 506 | thread->errors.write++; 507 | reconnect_socket(thread, c); 508 | } 509 | 510 | static void socket_readable(aeEventLoop *loop, int fd, void *data, int mask) { 511 | connection *c = data; 512 | size_t n; 513 | 514 | do { 515 | switch (sock.read(c, &n)) { 516 | case OK: break; 517 | case ERROR: goto error; 518 | case RETRY: return; 519 | } 520 | 521 | if (http_parser_execute(&c->parser, &parser_settings, c->buf, n) != n) goto error; 522 | if (n == 0 && !http_body_is_final(&c->parser)) goto error; 523 | 524 | c->thread->bytes += n; 525 | } while (1); 526 | /* } while (n == RECVBUF && sock.readable(c) > 0); */ 527 | 528 | return; 529 | 530 | error: 531 | c->thread->errors.read++; 532 | reconnect_socket(c->thread, c); 533 | } 534 | 535 | static uint64_t time_us() { 536 | struct timeval t; 537 | gettimeofday(&t, NULL); 538 | return (t.tv_sec * 1000000) + t.tv_usec; 539 | } 540 | 541 | static char *copy_url_part(char *url, struct http_parser_url *parts, enum http_parser_url_fields field) { 542 | char *part = NULL; 543 | 544 | if (parts->field_set & (1 << field)) { 545 | uint16_t off = parts->field_data[field].off; 546 | uint16_t len = parts->field_data[field].len; 547 | part = zcalloc(len + 1 * sizeof(char)); 548 | memcpy(part, &url[off], len); 549 | } 550 | 551 | return part; 552 | } 553 | 554 | static struct option longopts[] = { 555 | { "connections", required_argument, NULL, 'c' }, 556 | { "duration", required_argument, NULL, 'd' }, 557 | { "threads", required_argument, NULL, 't' }, 558 | { "script", required_argument, NULL, 's' }, 559 | { "header", required_argument, NULL, 'H' }, 560 | { "latency", no_argument, NULL, 'L' }, 561 | { "timeout", required_argument, NULL, 'T' }, 562 | { "iprange", required_argument, NULL, 'i' }, 563 | { "delay", required_argument, NULL, 'D' }, 564 | { "file-prefix", required_argument, NULL, 'f' }, 565 | { "help", no_argument, NULL, 'h' }, 566 | { "version", no_argument, NULL, 'v' }, 567 | { NULL, 0, NULL, 0 } 568 | }; 569 | 570 | static int parse_args(struct config *cfg, char **url, struct http_parser_url *parts, char **headers, int argc, char **argv) { 571 | char **header = headers; 572 | int c; 573 | 574 | memset(cfg, 0, sizeof(struct config)); 575 | cfg->threads = 2; 576 | cfg->connections = 10; 577 | cfg->duration = 10; 578 | cfg->timeout = SOCKET_TIMEOUT_MS; 579 | 580 | while ((c = getopt_long(argc, argv, "t:c:d:s:H:T:i:D:f:Lrv?", longopts, NULL)) != -1) { 581 | switch (c) { 582 | case 't': 583 | if (scan_metric(optarg, &cfg->threads)) return -1; 584 | break; 585 | case 'c': 586 | if (scan_metric(optarg, &cfg->connections)) return -1; 587 | break; 588 | case 'd': 589 | if (scan_time(optarg, &cfg->duration)) return -1; 590 | break; 591 | case 's': 592 | cfg->script = optarg; 593 | break; 594 | 595 | case 'i': 596 | cfg->iprange = optarg; 597 | break; 598 | 599 | case 'f': 600 | cfg->file_prefix = optarg; 601 | break; 602 | 603 | case 'D': 604 | if (scan_time(optarg, &cfg->send_delay)) return -1; 605 | break; 606 | 607 | case 'H': 608 | *header++ = optarg; 609 | break; 610 | case 'L': 611 | cfg->latency = true; 612 | break; 613 | case 'T': 614 | if (scan_time(optarg, &cfg->timeout)) return -1; 615 | cfg->timeout *= 1000; 616 | break; 617 | case 'v': 618 | printf("httpperf %s [%s] ", VERSION, aeGetApiName()); 619 | printf("Copyright (C) 2012 Will Glozer\n"); 620 | break; 621 | case 'h': 622 | case '?': 623 | case ':': 624 | default: 625 | return -1; 626 | } 627 | } 628 | 629 | if (optind == argc || !cfg->threads || !cfg->duration) return -1; 630 | 631 | if (!script_parse_url(argv[optind], parts)) { 632 | fprintf(stderr, "invalid URL: %s\n", argv[optind]); 633 | return -1; 634 | } 635 | 636 | if (!cfg->connections || cfg->connections < cfg->threads) { 637 | fprintf(stderr, "number of connections must be >= threads\n"); 638 | return -1; 639 | } 640 | 641 | if(cfg->iprange != NULL) 642 | { 643 | char *ip_start; 644 | char *ip_end; 645 | ip_start = strsep(&cfg->iprange, "-"); 646 | ip_end= strsep(&cfg->iprange, "-"); 647 | 648 | if(ip_start) 649 | { 650 | cfg->ip_start = (inet_addr(ip_start) == INADDR_NONE) ? 0 : ntohl(inet_addr(ip_start)); 651 | } 652 | 653 | if(ip_end) 654 | { 655 | cfg->ip_end = (inet_addr(ip_end) == INADDR_NONE) ? 0 : ntohl(inet_addr(ip_end)); 656 | } 657 | 658 | if(cfg->ip_start > cfg->ip_end) 659 | { 660 | uint32_t ip = cfg->ip_start; 661 | cfg->ip_start = cfg->ip_end; 662 | cfg->ip_end = ip; 663 | } 664 | 665 | printf("ip start 0x%x, ip end 0x%x \n", cfg->ip_start, cfg->ip_end); 666 | } 667 | else 668 | { 669 | cfg->ip_start = 0; 670 | cfg->ip_end = 0; 671 | } 672 | 673 | if(cfg->file_prefix) 674 | { 675 | printf("file prefix: %s \n", cfg->file_prefix); 676 | } 677 | 678 | printf("send delay %ld ms \n", cfg->send_delay); 679 | 680 | *url = argv[optind]; 681 | *header = NULL; 682 | 683 | return 0; 684 | } 685 | 686 | static void print_stats_header() { 687 | printf(" Thread Stats%6s%11s%8s%12s\n", "Avg", "Stdev", "Max", "+/- Stdev"); 688 | } 689 | 690 | static void print_units(long double n, char *(*fmt)(long double), int width) { 691 | char *msg = fmt(n); 692 | int len = strlen(msg), pad = 2; 693 | 694 | if (isalpha(msg[len-1])) pad--; 695 | if (isalpha(msg[len-2])) pad--; 696 | width -= pad; 697 | 698 | printf("%*.*s%.*s", width, width, msg, pad, " "); 699 | 700 | free(msg); 701 | } 702 | 703 | static void print_stats(char *name, stats *stats, char *(*fmt)(long double)) { 704 | uint64_t max = stats->max; 705 | long double mean = stats_mean(stats); 706 | long double stdev = stats_stdev(stats, mean); 707 | 708 | printf(" %-10s", name); 709 | print_units(mean, fmt, 8); 710 | print_units(stdev, fmt, 10); 711 | print_units(max, fmt, 9); 712 | printf("%8.2Lf%%\n", stats_within_stdev(stats, mean, stdev, 1)); 713 | } 714 | 715 | static void print_stats_latency(stats *stats) { 716 | long double percentiles[] = { 50.0, 75.0, 90.0, 99.0 }; 717 | printf(" Latency Distribution\n"); 718 | for (size_t i = 0; i < sizeof(percentiles) / sizeof(long double); i++) { 719 | long double p = percentiles[i]; 720 | uint64_t n = stats_percentile(stats, p); 721 | printf("%7.0Lf%%", p); 722 | print_units(n, format_time_us, 10); 723 | printf("\n"); 724 | } 725 | } 726 | --------------------------------------------------------------------------------