1
0
mirror of https://github.com/wg/wrk synced 2026-06-09 16:43:42 +08:00

15 Commits

26 changed files with 1301 additions and 1063 deletions
+13
View File
@@ -1,3 +1,16 @@
*.o
*.a
wrk
deps/luajit/src/host/buildvm
deps/luajit/src/host/buildvm_arch.h
deps/luajit/src/host/minilua
deps/luajit/src/jit/vmdef.lua
deps/luajit/src/lj_bcdef.h
deps/luajit/src/lj_ffdef.h
deps/luajit/src/lj_folddef.h
deps/luajit/src/lj_libdef.h
deps/luajit/src/lj_recdef.h
deps/luajit/src/lj_vm.s
deps/luajit/src/lua/
deps/luajit/src/luajit
+13
View File
@@ -0,0 +1,13 @@
wrk 4.0.0
* The wrk global variable is the only global defined by default.
* wrk.init() calls the global init(), remove calls to wrk.init().
* Add wrk.lookup(host, port) and wrk.connect(addr) functions.
* Add setup phase that calls the global setup() for each thread.
* Allow assignment to thread.addr to specify the server address.
* Add thread:set(name, value), thread:get(name), and thread:stop().
* Record latency for every request instead of random samples.
* Latency and requests in done() are now callable, not indexable.
* Only record timeouts when a response is actually received.
* Remove calibration phase and record rate at fixed interval.
* Improve correction of coordinated omission.
+8 -3
View File
@@ -1,7 +1,6 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Modified Apache 2.0 License
Version 2.0.1, February 2015
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
@@ -121,6 +120,12 @@
that such additional attribution notices cannot be construed
as modifying the License.
(e) If the Derivative Work includes substantial changes to features
or functionality of the Work, then you must remove the name of
the Work, and any derivation thereof, from all copies that you
distribute, whether in Source or Object form, except as required
in copyright, patent, trademark, and attribution notices.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
+2 -1
View File
@@ -9,6 +9,7 @@ ifeq ($(TARGET), sunos)
else ifeq ($(TARGET), darwin)
LDFLAGS += -pagezero_size 10000 -image_base 100000000
else ifeq ($(TARGET), linux)
CFLAGS += -D_POSIX_C_SOURCE=200112L -D_BSD_SOURCE
LIBS += -ldl
LDFLAGS += -Wl,-E
else ifeq ($(TARGET), freebsd)
@@ -17,7 +18,7 @@ else ifeq ($(TARGET), freebsd)
endif
SRC := wrk.c net.c ssl.c aprintf.c stats.c script.c units.c \
ae.c zmalloc.c http_parser.c tinymt64.c
ae.c zmalloc.c http_parser.c
BIN := wrk
ODIR := obj
-35
View File
@@ -106,38 +106,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
=========================================================================
== Tiny Mersenne Twister (TinyMT) Notice ==
=========================================================================
Copyright (c) 2011 Mutsuo Saito, Makoto Matsumoto, Hiroshima University
and The University of Tokyo. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the
distribution.
* Neither the name of the Hiroshima University nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+7 -67
View File
@@ -5,8 +5,8 @@ wrk - a HTTP benchmarking tool
design with scalable event notification systems such as epoll and kqueue.
An optional LuaJIT script can perform HTTP request generation, response
processing, and custom reporting. Several example scripts are located in
scripts/
processing, and custom reporting. Details are available in SCRIPTING and
several examples are located in scripts/
Basic Usage
@@ -26,64 +26,6 @@ Basic Usage
Requests/sec: 748868.53
Transfer/sec: 606.33MB
Scripting
wrk's public Lua API is:
init = function(args)
request = function()
response = function(status, headers, body)
done = function(summary, latency, requests)
wrk = {
scheme = "http",
host = "localhost",
port = nil,
method = "GET",
path = "/",
headers = {},
body = nil
}
function wrk.format(method, path, headers, body)
wrk.format returns a HTTP request string containing the passed
parameters merged with values from the wrk table.
global init -- function called when the thread is initialized
global request -- function returning the HTTP message for each request
global response -- optional function called with HTTP response data
global done -- optional function called with results of run
The init() function receives any extra command line arguments for the
script. Script arguments must be separated from wrk arguments with "--"
and scripts that override init() but not request() must call wrk.init()
The done() function receives a table containing result data, and two
statistics objects representing the sampled per-request latency and
per-thread request rate. Duration and latency are microsecond values
and rate is measured in requests per second.
latency.min -- minimum value seen
latency.max -- maximum value seen
latency.mean -- average value seen
latency.stdev -- standard deviation
latency:percentile(99.0) -- 99th percentile value
latency[i] -- raw sample value
summary = {
duration = N, -- run duration in microseconds
requests = N, -- total completed requests
bytes = N, -- total bytes received
errors = {
connect = N, -- total socket connection errors
read = N, -- total socket read errors
write = N, -- total socket write errors
status = N, -- total HTTP status codes > 399
timeout = N -- total request timeouts
}
}
Benchmarking Tips
The machine running wrk must have a sufficient number of ephemeral ports
@@ -92,15 +34,13 @@ Benchmarking Tips
than the number of concurrent connections being tested.
A user script that only changes the HTTP method, path, adds headers or
a body, will have no performance impact. If multiple HTTP requests are
necessary they should be pre-generated and returned via a quick lookup in
the request() call. Per-request actions, particularly building a new HTTP
request, and use of response() will necessarily reduce the amount of load
that can be generated.
a body, will have no performance impact. Per-request actions, particularly
building a new HTTP request, and use of response() will necessarily reduce
the amount of load that can be generated.
Acknowledgements
wrk contains code from a number of open source projects including the
'ae' event loop from redis, the nginx/joyent/node.js 'http-parser',
Mike Pall's LuaJIT, and the Tiny Mersenne Twister PRNG. Please consult
the NOTICE file for licensing details.
and Mike Pall's LuaJIT. Please consult the NOTICE file for licensing
details.
+112
View File
@@ -0,0 +1,112 @@
Overview
wrk supports executing a LuaJIT script during three distinct phases: setup,
running, and done. Each wrk thread has an independent scripting environment
and the setup & done phases execute in a separate environment which does
not participate in the running phase.
The public Lua API consists of a global table and a number of global
functions:
wrk = {
scheme = "http",
host = "localhost",
port = nil,
method = "GET",
path = "/",
headers = {},
body = nil,
thread = <userdata>,
}
function wrk.format(method, path, headers, body)
wrk.format returns a HTTP request string containing the passed parameters
merged with values from the wrk table.
function wrk.lookup(host, service)
wrk.lookup returns a table containing all known addresses for the host
and service pair. This corresponds to the POSIX getaddrinfo() function.
function wrk.connect(addr)
wrk.connect returns true if the address can be connected to, otherwise
it returns false. The address must be one returned from wrk.lookup().
The following globals are optional, and if defined must be functions:
global setup -- called during thread setup
global init -- called when the thread is starting
global request -- called to generate the HTTP request
global response -- called with HTTP response data
global done -- called with results of run
Setup
function setup(thread)
The setup phase begins after the target IP address has been resolved and all
threads have been initialized but not yet started.
setup() is called once for each thread and receives a userdata object
representing the thread.
thread.addr - get or set the thread's server address
thread:get(name) - get the value of a global in the thread's env
thread:set(name, value) - set the value of a global in the thread's env
thread:stop() - stop the thread
Only boolean, nil, number, and string values or tables of the same may be
transfered via get()/set() and thread:stop() can only be called while the
thread is running.
Running
function init(args)
function request()
function response(status, headers, body)
The running phase begins with a single call to init(), followed by
a call to request() and response() for each request cycle.
The init() function receives any extra command line arguments for the
script which must be separated from wrk arguments with "--".
request() returns a string containing the HTTP request. Building a new
request each time is expensive, when testing a high performance server
one solution is to pre-generate all requests in init() and do a quick
lookup in request().
response() is called with the HTTP response status, headers, and body.
Parsing the headers and body is expensive, so if the response global is
nil after the call to init() wrk will ignore the headers and body.
Done
function done(summary, latency, requests)
The done() function receives a table containing result data, and two
statistics objects representing the per-request latency and per-thread
request rate. Duration and latency are microsecond values and rate is
measured in requests per second.
latency.min -- minimum value seen
latency.max -- maximum value seen
latency.mean -- average value seen
latency.stdev -- standard deviation
latency:percentile(99.0) -- 99th percentile value
latency(i) -- raw value and count
summary = {
duration = N, -- run duration in microseconds
requests = N, -- total completed requests
bytes = N, -- total bytes received
errors = {
connect = N, -- total socket connection errors
read = N, -- total socket read errors
write = N, -- total socket write errors
status = N, -- total HTTP status codes > 399
timeout = N -- total request timeouts
}
}
+22
View File
@@ -0,0 +1,22 @@
-- example script that demonstrates use of setup() to pass
-- a random server address to each thread
local addrs = nil
function setup(thread)
if not addrs then
addrs = wrk.lookup(wrk.host, wrk.port or "http")
for i = #addrs, 1, -1 do
if not wrk.connect(addrs[i]) then
table.remove(addrs, i)
end
end
end
thread.addr = addrs[math.random(#addrs)]
end
function init(args)
local msg = "thread addr: %s"
print(msg:format(wrk.thread.addr))
end
-2
View File
@@ -1,8 +1,6 @@
-- example script demonstrating HTTP pipelining
init = function(args)
wrk.init(args)
local r = {}
r[1] = wrk.format(nil, "/?foo")
r[2] = wrk.format(nil, "/?bar")
+38
View File
@@ -0,0 +1,38 @@
-- example script that demonstrates use of setup() to pass
-- data to and from the threads
local counter = 1
local threads = {}
function setup(thread)
thread:set("id", counter)
table.insert(threads, thread)
counter = counter + 1
end
function init(args)
requests = 0
responses = 0
local msg = "thread %d created"
print(msg:format(id))
end
function request()
requests = requests + 1
return wrk.request()
end
function response(status, headers, body)
responses = responses + 1
end
function done(summary, latency, requests)
for index, thread in ipairs(threads) do
local id = thread:get("id")
local requests = thread:get("requests")
local responses = thread:get("responses")
local msg = "thread %d made %d requests and got %d responses"
print(msg:format(id, requests, responses))
end
end
+10
View File
@@ -0,0 +1,10 @@
-- example script that demonstrates use of thread:stop()
local counter = 1
function response()
if counter == 100 then
wrk.thread:stop()
end
counter = counter + 1
end
+5 -1
View File
@@ -5,9 +5,13 @@
#define HAVE_KQUEUE
#elif defined(__linux__)
#define HAVE_EPOLL
#define _POSIX_C_SOURCE 200809L
#elif defined (__sun)
#define HAVE_EVPORT
#define _XPG6
#define __EXTENSIONS__
#include <stropts.h>
#include <sys/filio.h>
#include <sys/time.h>
#endif
#endif /* CONFIG_H */
+483 -246
View File
File diff suppressed because it is too large Load Diff
+45 -19
View File
@@ -24,8 +24,10 @@
extern "C" {
#endif
/* Also update SONAME in the Makefile whenever you change these. */
#define HTTP_PARSER_VERSION_MAJOR 2
#define HTTP_PARSER_VERSION_MINOR 1
#define HTTP_PARSER_VERSION_MINOR 4
#define HTTP_PARSER_VERSION_PATCH 2
#include <sys/types.h>
#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)
@@ -50,9 +52,16 @@ typedef unsigned __int64 uint64_t;
# define HTTP_PARSER_STRICT 1
#endif
/* Maximium header size allowed */
#define HTTP_MAX_HEADER_SIZE (80*1024)
/* Maximium header size allowed. If the macro is not defined
* before including this header then the default is used. To
* change the maximum header size, define the macro in the build
* environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
* the effective limit on the size of the header, define the macro
* to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
*/
#ifndef HTTP_MAX_HEADER_SIZE
# define HTTP_MAX_HEADER_SIZE (80*1024)
#endif
typedef struct http_parser http_parser;
typedef struct http_parser_settings http_parser_settings;
@@ -67,7 +76,7 @@ typedef struct http_parser_settings http_parser_settings;
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
* chunked' headers that indicate the presence of a body.
*
* http_data_cb does not return data chunks. It will be call arbitrarally
* http_data_cb does not return data chunks. It will be called arbitrarily
* many times for each string. E.G. you might get 10 callbacks for "on_url"
* each providing just a few characters more data.
*/
@@ -108,6 +117,8 @@ typedef int (*http_cb) (http_parser*);
/* RFC-5789 */ \
XX(24, PATCH, PATCH) \
XX(25, PURGE, PURGE) \
/* CalDAV */ \
XX(26, MKCALENDAR, MKCALENDAR) \
enum http_method
{
@@ -125,9 +136,10 @@ enum flags
{ F_CHUNKED = 1 << 0
, F_CONNECTION_KEEP_ALIVE = 1 << 1
, F_CONNECTION_CLOSE = 1 << 2
, F_TRAILING = 1 << 3
, F_UPGRADE = 1 << 4
, F_SKIPBODY = 1 << 5
, F_CONNECTION_UPGRADE = 1 << 3
, F_TRAILING = 1 << 4
, F_UPGRADE = 1 << 5
, F_SKIPBODY = 1 << 6
};
@@ -141,13 +153,13 @@ enum flags
\
/* Callback-related errors */ \
XX(CB_message_begin, "the on_message_begin callback failed") \
XX(CB_status_complete, "the on_status_complete callback failed") \
XX(CB_url, "the on_url callback failed") \
XX(CB_header_field, "the on_header_field callback failed") \
XX(CB_header_value, "the on_header_value callback failed") \
XX(CB_headers_complete, "the on_headers_complete callback failed") \
XX(CB_body, "the on_body callback failed") \
XX(CB_message_complete, "the on_message_complete callback failed") \
XX(CB_status, "the on_status callback failed") \
\
/* Parsing-related errors */ \
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
@@ -191,11 +203,11 @@ enum http_errno {
struct http_parser {
/** PRIVATE **/
unsigned char type : 2; /* enum http_parser_type */
unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */
unsigned char state; /* enum state from http_parser.c */
unsigned char header_state; /* enum header_state from http_parser.c */
unsigned char index; /* index into current matcher */
unsigned int type : 2; /* enum http_parser_type */
unsigned int flags : 6; /* F_* values from 'flags' enum; semi-public */
unsigned int state : 8; /* enum state from http_parser.c */
unsigned int header_state : 8; /* enum header_state from http_parser.c */
unsigned int index : 8; /* index into current matcher */
uint32_t nread; /* # bytes read in various scenarios */
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
@@ -203,16 +215,16 @@ struct http_parser {
/** READ-ONLY **/
unsigned short http_major;
unsigned short http_minor;
unsigned short status_code; /* responses only */
unsigned char method; /* requests only */
unsigned char http_errno : 7;
unsigned int status_code : 16; /* responses only */
unsigned int method : 8; /* requests only */
unsigned int http_errno : 7;
/* 1 = Upgrade header was present and the parser has exited because of that.
* 0 = No upgrade header present.
* Should be checked when http_parser_execute() returns in addition to
* error checking.
*/
unsigned char upgrade : 1;
unsigned int upgrade : 1;
/** PUBLIC **/
void *data; /* A pointer to get hook to the "connection" or "socket" object */
@@ -222,7 +234,7 @@ struct http_parser {
struct http_parser_settings {
http_cb on_message_begin;
http_data_cb on_url;
http_cb on_status_complete;
http_data_cb on_status;
http_data_cb on_header_field;
http_data_cb on_header_value;
http_cb on_headers_complete;
@@ -261,9 +273,23 @@ struct http_parser_url {
};
/* Returns the library version. Bits 16-23 contain the major version number,
* bits 8-15 the minor version number and bits 0-7 the patch level.
* Usage example:
*
* unsigned long version = http_parser_version();
* unsigned major = (version >> 16) & 255;
* unsigned minor = (version >> 8) & 255;
* unsigned patch = version & 255;
* printf("http_parser v%u.%u.%u\n", major, minor, patch);
*/
unsigned long http_parser_version(void);
void http_parser_init(http_parser *parser, enum http_parser_type type);
/* Executes the parser. Returns number of parsed bytes. Sets
* `parser->http_errno` on error. */
size_t http_parser_execute(http_parser *parser,
const http_parser_settings *settings,
const char *data,
+3 -7
View File
@@ -6,7 +6,6 @@
#include <fcntl.h>
#include <getopt.h>
#include <math.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdarg.h>
@@ -17,7 +16,6 @@
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/uio.h>
@@ -33,9 +31,7 @@ static void *thread_main(void *);
static int connect_socket(thread *, connection *);
static int reconnect_socket(thread *, connection *);
static int calibrate(aeEventLoop *, long long, void *);
static int sample_rate(aeEventLoop *, long long, void *);
static int check_timeouts(aeEventLoop *, long long, void *);
static int record_rate(aeEventLoop *, long long, void *);
static void socket_connected(aeEventLoop *, int, void *, int);
static void socket_writeable(aeEventLoop *, int, void *, int);
@@ -48,9 +44,9 @@ static int response_body(http_parser *, const char *, size_t);
static uint64_t time_us();
static char *extract_url_part(char *, struct http_parser_url *, enum http_parser_url_fields);
static int parse_args(struct config *, char **, struct http_parser_url *, char **, int, char **);
static char *copy_url_part(char *, struct http_parser_url *, enum http_parser_url_fields);
static int parse_args(struct config *, char **, char **, int, char **);
static void print_stats_header();
static void print_stats(char *, stats *, char *(*)(long double));
static void print_stats_latency(stats *);
+1 -1
View File
@@ -17,7 +17,7 @@ status sock_close(connection *c) {
status sock_read(connection *c, size_t *n) {
ssize_t r = read(c->fd, c->buf, sizeof(c->buf));
*n = (size_t) r;
return r > 0 ? OK : ERROR;
return r >= 0 ? OK : ERROR;
}
status sock_write(connection *c, char *buf, size_t len, size_t *n) {
+1 -1
View File
@@ -1,9 +1,9 @@
#ifndef NET_H
#define NET_H
#include "config.h"
#include <stdint.h>
#include <openssl/ssl.h>
#include "wrk.h"
typedef enum {
+341 -75
View File
@@ -4,6 +4,7 @@
#include <string.h>
#include "script.h"
#include "http_parser.h"
#include "zmalloc.h"
typedef struct {
char *name;
@@ -11,76 +12,148 @@ typedef struct {
void *value;
} table_field;
static int script_addr_tostring(lua_State *);
static int script_addr_gc(lua_State *);
static int script_stats_call(lua_State *);
static int script_stats_len(lua_State *);
static int script_stats_get(lua_State *);
static void set_fields(lua_State *, int index, const table_field *);
static int script_stats_index(lua_State *);
static int script_thread_index(lua_State *);
static int script_thread_newindex(lua_State *);
static int script_wrk_lookup(lua_State *);
static int script_wrk_connect(lua_State *);
static const struct luaL_reg statslib[] = {
{ "__index", script_stats_get },
{ "__len", script_stats_len },
{ NULL, NULL }
static void set_fields(lua_State *, int, const table_field *);
static void set_field(lua_State *, int, char *, int);
static int push_url_part(lua_State *, char *, struct http_parser_url *, enum http_parser_url_fields);
static const struct luaL_reg addrlib[] = {
{ "__tostring", script_addr_tostring },
{ "__gc" , script_addr_gc },
{ NULL, NULL }
};
lua_State *script_create(char *scheme, char *host, char *port, char *path) {
static const struct luaL_reg statslib[] = {
{ "__call", script_stats_call },
{ "__index", script_stats_index },
{ "__len", script_stats_len },
{ NULL, NULL }
};
static const struct luaL_reg threadlib[] = {
{ "__index", script_thread_index },
{ "__newindex", script_thread_newindex },
{ NULL, NULL }
};
lua_State *script_create(char *file, char *url, char **headers) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_dostring(L, "wrk = require \"wrk\"");
(void) luaL_dostring(L, "wrk = require \"wrk\"");
luaL_newmetatable(L, "wrk.addr");
luaL_register(L, NULL, addrlib);
luaL_newmetatable(L, "wrk.stats");
luaL_register(L, NULL, statslib);
lua_pop(L, 1);
luaL_newmetatable(L, "wrk.thread");
luaL_register(L, NULL, threadlib);
struct http_parser_url parts = {};
script_parse_url(url, &parts);
char *path = "/";
if (parts.field_set & (1 << UF_PATH)) {
path = &url[parts.field_data[UF_PATH].off];
}
const table_field fields[] = {
{ "scheme", LUA_TSTRING, scheme },
{ "host", LUA_TSTRING, host },
{ "port", LUA_TSTRING, port },
{ "path", LUA_TSTRING, path },
{ NULL, 0, NULL },
{ "lookup", LUA_TFUNCTION, script_wrk_lookup },
{ "connect", LUA_TFUNCTION, script_wrk_connect },
{ "path", LUA_TSTRING, path },
{ NULL, 0, NULL },
};
lua_getglobal(L, "wrk");
set_fields(L, 1, fields);
lua_pop(L, 1);
return L;
}
set_field(L, 4, "scheme", push_url_part(L, url, &parts, UF_SCHEMA));
set_field(L, 4, "host", push_url_part(L, url, &parts, UF_HOST));
set_field(L, 4, "port", push_url_part(L, url, &parts, UF_PORT));
set_fields(L, 4, fields);
void script_headers(lua_State *L, char **headers) {
lua_getglobal(L, "wrk");
lua_getfield(L, 1, "headers");
lua_getfield(L, 4, "headers");
for (char **h = headers; *h; h++) {
char *p = strchr(*h, ':');
if (p && p[1] == ' ') {
lua_pushlstring(L, *h, p - *h);
lua_pushstring(L, p + 2);
lua_settable(L, 2);
lua_settable(L, 5);
}
}
lua_pop(L, 2);
lua_pop(L, 5);
if (file && luaL_dofile(L, file)) {
const char *cause = lua_tostring(L, -1);
fprintf(stderr, "%s: %s\n", file, cause);
}
return L;
}
void script_init(lua_State *L, char *script, int argc, char **argv) {
if (script && luaL_dofile(L, script)) {
const char *cause = lua_tostring(L, -1);
fprintf(stderr, "%s: %s\n", script, cause);
}
bool script_resolve(lua_State *L, char *host, char *service) {
lua_getglobal(L, "wrk");
lua_getglobal(L, "init");
lua_newtable(L);
for (int i = 0; i < argc; i++) {
lua_pushstring(L, argv[i]);
lua_rawseti(L, 2, i);
}
lua_getfield(L, -1, "resolve");
lua_pushstring(L, host);
lua_pushstring(L, service);
lua_call(L, 2, 0);
lua_getfield(L, -1, "addrs");
size_t count = lua_objlen(L, -1);
lua_pop(L, 2);
return count > 0;
}
void script_push_thread(lua_State *L, thread *t) {
thread **ptr = (thread **) lua_newuserdata(L, sizeof(thread **));
*ptr = t;
luaL_getmetatable(L, "wrk.thread");
lua_setmetatable(L, -2);
}
void script_init(lua_State *L, thread *t, int argc, char **argv) {
lua_getglobal(t->L, "wrk");
script_push_thread(t->L, t);
lua_setfield(t->L, -2, "thread");
lua_getglobal(L, "wrk");
lua_getfield(L, -1, "setup");
script_push_thread(L, t);
lua_call(L, 1, 0);
lua_pop(L, 1);
lua_getfield(t->L, -1, "init");
lua_newtable(t->L);
for (int i = 0; i < argc; i++) {
lua_pushstring(t->L, argv[i]);
lua_rawseti(t->L, -2, i);
}
lua_call(t->L, 1, 0);
lua_pop(t->L, 1);
}
void script_request(lua_State *L, char **buf, size_t *len) {
int pop = 1;
lua_getglobal(L, "request");
if (!lua_isfunction(L, -1)) {
lua_getglobal(L, "wrk");
lua_getfield(L, -1, "request");
pop += 2;
}
lua_call(L, 0, 1);
const char *str = lua_tolstring(L, 1, len);
const char *str = lua_tolstring(L, -1, len);
*buf = realloc(*buf, *len);
memcpy(*buf, str, *len);
lua_pop(L, 1);
lua_pop(L, pop);
}
void script_response(lua_State *L, int status, buffer *headers, buffer *body) {
@@ -101,27 +174,23 @@ void script_response(lua_State *L, int status, buffer *headers, buffer *body) {
buffer_reset(body);
}
bool script_is_function(lua_State *L, char *name) {
lua_getglobal(L, name);
bool is_function = lua_isfunction(L, -1);
lua_pop(L, 1);
return is_function;
}
bool script_is_static(lua_State *L) {
lua_getglobal(L, "wrk");
lua_getfield(L, 1, "request");
lua_getglobal(L, "request");
bool is_static = lua_equal(L, 2, 3);
lua_pop(L, 3);
return is_static;
return !script_is_function(L, "request");
}
bool script_want_response(lua_State *L) {
lua_getglobal(L, "response");
bool defined = lua_type(L, 1) == LUA_TFUNCTION;
lua_pop(L, 1);
return defined;
return script_is_function(L, "response");
}
bool script_has_done(lua_State *L) {
lua_getglobal(L, "done");
bool defined = lua_type(L, 1) == LUA_TFUNCTION;
lua_pop(L, 1);
return defined;
return script_is_function(L, "done");
}
void script_header_done(lua_State *L, luaL_Buffer *buffer) {
@@ -160,21 +229,19 @@ void script_errors(lua_State *L, errors *errors) {
lua_setfield(L, 1, "errors");
}
void script_done(lua_State *L, stats *latency, stats *requests) {
stats **s;
void script_push_stats(lua_State *L, stats *s) {
stats **ptr = (stats **) lua_newuserdata(L, sizeof(stats **));
*ptr = s;
luaL_getmetatable(L, "wrk.stats");
lua_setmetatable(L, -2);
}
void script_done(lua_State *L, stats *latency, stats *requests) {
lua_getglobal(L, "done");
lua_pushvalue(L, 1);
s = (stats **) lua_newuserdata(L, sizeof(stats **));
*s = latency;
luaL_getmetatable(L, "wrk.stats");
lua_setmetatable(L, 4);
s = (stats **) lua_newuserdata(L, sizeof(stats **));
*s = requests;
luaL_getmetatable(L, "wrk.stats");
lua_setmetatable(L, 5);
script_push_stats(L, latency);
script_push_stats(L, requests);
lua_call(L, 3, 0);
lua_pop(L, 1);
@@ -221,6 +288,48 @@ size_t script_verify_request(lua_State *L) {
return count;
}
static struct addrinfo *checkaddr(lua_State *L) {
struct addrinfo *addr = luaL_checkudata(L, -1, "wrk.addr");
luaL_argcheck(L, addr != NULL, 1, "`addr' expected");
return addr;
}
void script_addr_copy(struct addrinfo *src, struct addrinfo *dst) {
*dst = *src;
dst->ai_addr = zmalloc(src->ai_addrlen);
memcpy(dst->ai_addr, src->ai_addr, src->ai_addrlen);
}
struct addrinfo *script_addr_clone(lua_State *L, struct addrinfo *addr) {
struct addrinfo *udata = lua_newuserdata(L, sizeof(*udata));
luaL_getmetatable(L, "wrk.addr");
lua_setmetatable(L, -2);
script_addr_copy(addr, udata);
return udata;
}
static int script_addr_tostring(lua_State *L) {
struct addrinfo *addr = checkaddr(L);
char host[NI_MAXHOST];
char service[NI_MAXSERV];
int flags = NI_NUMERICHOST | NI_NUMERICSERV;
int rc = getnameinfo(addr->ai_addr, addr->ai_addrlen, host, NI_MAXHOST, service, NI_MAXSERV, flags);
if (rc != 0) {
const char *msg = gai_strerror(rc);
return luaL_error(L, "addr tostring failed %s", msg);
}
lua_pushfstring(L, "%s:%s", host, service);
return 1;
}
static int script_addr_gc(lua_State *L) {
struct addrinfo *addr = checkaddr(L);
zfree(addr->ai_addr);
return 0;
}
static stats *checkstats(lua_State *L) {
stats **s = luaL_checkudata(L, 1, "wrk.stats");
luaL_argcheck(L, s != NULL, 1, "`stats' expected");
@@ -234,34 +343,191 @@ static int script_stats_percentile(lua_State *L) {
return 1;
}
static int script_stats_get(lua_State *L) {
static int script_stats_call(lua_State *L) {
stats *s = checkstats(L);
if (lua_isnumber(L, 2)) {
int index = luaL_checkint(L, 2);
lua_pushnumber(L, s->data[index - 1]);
} else if (lua_isstring(L, 2)) {
const char *method = lua_tostring(L, 2);
if (!strcmp("min", method)) lua_pushnumber(L, s->min);
if (!strcmp("max", method)) lua_pushnumber(L, s->max);
if (!strcmp("mean", method)) lua_pushnumber(L, stats_mean(s));
if (!strcmp("stdev", method)) lua_pushnumber(L, stats_stdev(s, stats_mean(s)));
if (!strcmp("percentile", method)) {
lua_pushcfunction(L, script_stats_percentile);
}
uint64_t index = lua_tonumber(L, 2);
uint64_t count;
lua_pushnumber(L, stats_value_at(s, index - 1, &count));
lua_pushnumber(L, count);
return 2;
}
static int script_stats_index(lua_State *L) {
stats *s = checkstats(L);
const char *method = lua_tostring(L, 2);
if (!strcmp("min", method)) lua_pushnumber(L, s->min);
if (!strcmp("max", method)) lua_pushnumber(L, s->max);
if (!strcmp("mean", method)) lua_pushnumber(L, stats_mean(s));
if (!strcmp("stdev", method)) lua_pushnumber(L, stats_stdev(s, stats_mean(s)));
if (!strcmp("percentile", method)) {
lua_pushcfunction(L, script_stats_percentile);
}
return 1;
}
static int script_stats_len(lua_State *L) {
stats *s = checkstats(L);
lua_pushinteger(L, s->limit);
lua_pushinteger(L, stats_popcount(s));
return 1;
}
static thread *checkthread(lua_State *L) {
thread **t = luaL_checkudata(L, 1, "wrk.thread");
luaL_argcheck(L, t != NULL, 1, "`thread' expected");
return *t;
}
static int script_thread_get(lua_State *L) {
thread *t = checkthread(L);
const char *key = lua_tostring(L, -1);
lua_getglobal(t->L, key);
script_copy_value(t->L, L, -1);
lua_pop(t->L, 1);
return 1;
}
static int script_thread_set(lua_State *L) {
thread *t = checkthread(L);
const char *name = lua_tostring(L, -2);
script_copy_value(L, t->L, -1);
lua_setglobal(t->L, name);
return 0;
}
static int script_thread_stop(lua_State *L) {
thread *t = checkthread(L);
aeStop(t->loop);
return 0;
}
static int script_thread_index(lua_State *L) {
thread *t = checkthread(L);
const char *key = lua_tostring(L, 2);
if (!strcmp("get", key)) lua_pushcfunction(L, script_thread_get);
if (!strcmp("set", key)) lua_pushcfunction(L, script_thread_set);
if (!strcmp("stop", key)) lua_pushcfunction(L, script_thread_stop);
if (!strcmp("addr", key)) script_addr_clone(L, t->addr);
return 1;
}
static int script_thread_newindex(lua_State *L) {
thread *t = checkthread(L);
const char *key = lua_tostring(L, -2);
if (!strcmp("addr", key)) {
struct addrinfo *addr = checkaddr(L);
if (t->addr) zfree(t->addr->ai_addr);
t->addr = zrealloc(t->addr, sizeof(*addr));
script_addr_copy(addr, t->addr);
} else {
luaL_error(L, "cannot set '%s' on thread", luaL_typename(L, -1));
}
return 0;
}
static int script_wrk_lookup(lua_State *L) {
struct addrinfo *addrs;
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM
};
int rc, index = 1;
const char *host = lua_tostring(L, -2);
const char *service = lua_tostring(L, -1);
if ((rc = getaddrinfo(host, service, &hints, &addrs)) != 0) {
const char *msg = gai_strerror(rc);
fprintf(stderr, "unable to resolve %s:%s %s\n", host, service, msg);
exit(1);
}
lua_newtable(L);
for (struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next) {
script_addr_clone(L, addr);
lua_rawseti(L, -2, index++);
}
freeaddrinfo(addrs);
return 1;
}
static int script_wrk_connect(lua_State *L) {
struct addrinfo *addr = checkaddr(L);
int fd, connected = 0;
if ((fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) != -1) {
connected = connect(fd, addr->ai_addr, addr->ai_addrlen) == 0;
close(fd);
}
lua_pushboolean(L, connected);
return 1;
}
void script_copy_value(lua_State *src, lua_State *dst, int index) {
switch (lua_type(src, index)) {
case LUA_TBOOLEAN:
lua_pushboolean(dst, lua_toboolean(src, index));
break;
case LUA_TNIL:
lua_pushnil(dst);
break;
case LUA_TNUMBER:
lua_pushnumber(dst, lua_tonumber(src, index));
break;
case LUA_TSTRING:
lua_pushstring(dst, lua_tostring(src, index));
break;
case LUA_TTABLE:
lua_newtable(dst);
lua_pushnil(src);
while (lua_next(src, index - 1)) {
script_copy_value(src, dst, -1);
script_copy_value(src, dst, -2);
lua_settable(dst, -3);
lua_pop(src, 1);
}
lua_pop(src, 1);
break;
default:
luaL_error(src, "cannot transfer '%s' to thread", luaL_typename(src, index));
}
}
int script_parse_url(char *url, struct http_parser_url *parts) {
if (!http_parser_parse_url(url, strlen(url), 0, parts)) {
if (!(parts->field_set & (1 << UF_SCHEMA))) return 0;
if (!(parts->field_set & (1 << UF_HOST))) return 0;
return 1;
}
return 0;
}
static int push_url_part(lua_State *L, char *url, struct http_parser_url *parts, enum http_parser_url_fields field) {
int type = parts->field_set & (1 << field) ? LUA_TSTRING : LUA_TNIL;
uint16_t off, len;
switch (type) {
case LUA_TSTRING:
off = parts->field_data[field].off;
len = parts->field_data[field].len;
lua_pushlstring(L, &url[off], len);
break;
case LUA_TNIL:
lua_pushnil(L);
}
return type;
}
static void set_field(lua_State *L, int index, char *field, int type) {
(void) type;
lua_setfield(L, index, field);
}
static void set_fields(lua_State *L, int index, const table_field *fields) {
for (int i = 0; fields[i].name; i++) {
table_field f = fields[i];
switch (f.value == NULL ? LUA_TNIL : f.type) {
case LUA_TFUNCTION:
lua_pushcfunction(L, (lua_CFunction) f.value);
break;
case LUA_TNUMBER:
lua_pushinteger(L, *((lua_Integer *) f.value));
break;
+11 -10
View File
@@ -5,22 +5,20 @@
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <unistd.h>
#include "stats.h"
#include "wrk.h"
typedef struct {
char *buffer;
size_t length;
char *cursor;
} buffer;
lua_State *script_create(char *, char *, char **);
lua_State *script_create(char *, char *, char *, char *);
void script_headers(lua_State *, char **);
size_t script_verify_request(lua_State *L);
void script_init(lua_State *, char *, int, char **);
bool script_resolve(lua_State *, char *, char *);
void script_setup(lua_State *, thread *);
void script_done(lua_State *, stats *, stats *);
void script_init(lua_State *, thread *, int, char **);
void script_request(lua_State *, char **, size_t *);
void script_response(lua_State *, int, buffer *, buffer *);
size_t script_verify_request(lua_State *L);
bool script_is_static(lua_State *);
bool script_want_response(lua_State *L);
@@ -28,6 +26,9 @@ bool script_has_done(lua_State *L);
void script_summary(lua_State *, uint64_t, uint64_t, uint64_t);
void script_errors(lua_State *, errors *);
void script_copy_value(lua_State *, lua_State *, int);
int script_parse_url(char *, struct http_parser_url *);
void buffer_append(buffer *, const char *, size_t);
void buffer_reset(buffer *);
char *buffer_pushlstring(lua_State *, char *);
+62 -58
View File
@@ -7,10 +7,11 @@
#include "stats.h"
#include "zmalloc.h"
stats *stats_alloc(uint64_t samples) {
stats *s = zcalloc(sizeof(stats) + sizeof(uint64_t) * samples);
s->samples = samples;
s->min = UINT64_MAX;
stats *stats_alloc(uint64_t max) {
uint64_t limit = max + 1;
stats *s = zcalloc(sizeof(stats) + sizeof(uint64_t) * limit);
s->limit = limit;
s->min = UINT64_MAX;
return s;
}
@@ -18,54 +19,48 @@ void stats_free(stats *stats) {
zfree(stats);
}
void stats_reset(stats *stats) {
stats->limit = 0;
stats->index = 0;
stats->min = UINT64_MAX;
stats->max = 0;
int stats_record(stats *stats, uint64_t n) {
if (n >= stats->limit) return 0;
__sync_fetch_and_add(&stats->data[n], 1);
__sync_fetch_and_add(&stats->count, 1);
uint64_t min = stats->min;
uint64_t max = stats->max;
while (n < min) min = __sync_val_compare_and_swap(&stats->min, min, n);
while (n > max) max = __sync_val_compare_and_swap(&stats->max, max, n);
return 1;
}
void stats_rewind(stats *stats) {
stats->limit = 0;
stats->index = 0;
}
void stats_record(stats *stats, uint64_t x) {
stats->data[stats->index++] = x;
if (x < stats->min) stats->min = x;
if (x > stats->max) stats->max = x;
if (stats->limit < stats->samples) stats->limit++;
if (stats->index == stats->samples) stats->index = 0;
}
static int stats_compare(const void *a, const void *b) {
uint64_t *x = (uint64_t *) a;
uint64_t *y = (uint64_t *) b;
return *x - *y;
}
long double stats_summarize(stats *stats) {
qsort(stats->data, stats->limit, sizeof(uint64_t), &stats_compare);
return stats_mean(stats);
void stats_correct(stats *stats, int64_t expected) {
for (uint64_t n = expected * 2; n <= stats->max; n++) {
uint64_t count = stats->data[n];
int64_t m = (int64_t) n - expected;
while (count && m > expected) {
stats->data[m] += count;
stats->count += count;
m -= expected;
}
}
}
long double stats_mean(stats *stats) {
if (stats->limit == 0) return 0.0;
if (stats->count == 0) return 0.0;
uint64_t sum = 0;
for (uint64_t i = 0; i < stats->limit; i++) {
sum += stats->data[i];
for (uint64_t i = stats->min; i <= stats->max; i++) {
sum += stats->data[i] * i;
}
return sum / (long double) stats->limit;
return sum / (long double) stats->count;
}
long double stats_stdev(stats *stats, long double mean) {
long double sum = 0.0;
if (stats->limit < 2) return 0.0;
for (uint64_t i = 0; i < stats->limit; i++) {
sum += powl(stats->data[i] - mean, 2);
if (stats->count < 2) return 0.0;
for (uint64_t i = stats->min; i <= stats->max; i++) {
if (stats->data[i]) {
sum += powl(i - mean, 2) * stats->data[i];
}
}
return sqrtl(sum / (stats->limit - 1));
return sqrtl(sum / (stats->count - 1));
}
long double stats_within_stdev(stats *stats, long double mean, long double stdev, uint64_t n) {
@@ -73,31 +68,40 @@ long double stats_within_stdev(stats *stats, long double mean, long double stdev
long double lower = mean - (stdev * n);
uint64_t sum = 0;
for (uint64_t i = 0; i < stats->limit; i++) {
uint64_t x = stats->data[i];
if (x >= lower && x <= upper) sum++;
for (uint64_t i = stats->min; i <= stats->max; i++) {
if (i >= lower && i <= upper) {
sum += stats->data[i];
}
}
return (sum / (long double) stats->limit) * 100;
return (sum / (long double) stats->count) * 100;
}
uint64_t stats_percentile(stats *stats, long double p) {
uint64_t rank = round((p / 100.0) * stats->limit + 0.5);
return stats->data[rank - 1];
}
void stats_sample(stats *dst, tinymt64_t *state, uint64_t count, stats *src) {
for (uint64_t i = 0; i < count; i++) {
uint64_t n = rand64(state, src->limit);
stats_record(dst, src->data[n]);
uint64_t rank = round((p / 100.0) * stats->count + 0.5);
uint64_t total = 0;
for (uint64_t i = stats->min; i <= stats->max; i++) {
total += stats->data[i];
if (total >= rank) return i;
}
return 0;
}
uint64_t rand64(tinymt64_t *state, uint64_t n) {
uint64_t x, max = ~UINT64_C(0);
max -= max % n;
do {
x = tinymt64_generate_uint64(state);
} while (x >= max);
return x % n;
uint64_t stats_popcount(stats *stats) {
uint64_t count = 0;
for (uint64_t i = stats->min; i <= stats->max; i++) {
if (stats->data[i]) count++;
}
return count;
}
uint64_t stats_value_at(stats *stats, uint64_t index, uint64_t *count) {
*count = 0;
for (uint64_t i = stats->min; i <= stats->max; i++) {
if (stats->data[i] && (*count)++ == index) {
*count = stats->data[i];
return i;
}
}
return 0;
}
+6 -9
View File
@@ -2,7 +2,7 @@
#define STATS_H
#include <stdbool.h>
#include "tinymt64.h"
#include <stdint.h>
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
@@ -16,8 +16,7 @@ typedef struct {
} errors;
typedef struct {
uint64_t samples;
uint64_t index;
uint64_t count;
uint64_t limit;
uint64_t min;
uint64_t max;
@@ -26,18 +25,16 @@ typedef struct {
stats *stats_alloc(uint64_t);
void stats_free(stats *);
void stats_reset(stats *);
void stats_rewind(stats *);
void stats_record(stats *, uint64_t);
int stats_record(stats *, uint64_t);
void stats_correct(stats *, int64_t);
long double stats_summarize(stats *);
long double stats_mean(stats *);
long double stats_stdev(stats *stats, long double);
long double stats_within_stdev(stats *, long double, long double, uint64_t);
uint64_t stats_percentile(stats *, long double);
void stats_sample(stats *, tinymt64_t *, uint64_t, stats *);
uint64_t rand64(tinymt64_t *, uint64_t);
uint64_t stats_popcount(stats *);
uint64_t stats_value_at(stats *stats, uint64_t, uint64_t *);
#endif /* STATS_H */
-129
View File
@@ -1,129 +0,0 @@
/**
* @file tinymt64.c
*
* @brief 64-bit Tiny Mersenne Twister only 127 bit internal state
*
* @author Mutsuo Saito (Hiroshima University)
* @author Makoto Matsumoto (The University of Tokyo)
*
* Copyright (C) 2011 Mutsuo Saito, Makoto Matsumoto,
* Hiroshima University and The University of Tokyo.
* All rights reserved.
*
* The 3-clause BSD License is applied to this software, see
* LICENSE.txt
*/
#include "tinymt64.h"
#define MIN_LOOP 8
/**
* This function represents a function used in the initialization
* by init_by_array
* @param[in] x 64-bit integer
* @return 64-bit integer
*/
static uint64_t ini_func1(uint64_t x) {
return (x ^ (x >> 59)) * UINT64_C(2173292883993);
}
/**
* This function represents a function used in the initialization
* by init_by_array
* @param[in] x 64-bit integer
* @return 64-bit integer
*/
static uint64_t ini_func2(uint64_t x) {
return (x ^ (x >> 59)) * UINT64_C(58885565329898161);
}
/**
* This function certificate the period of 2^127-1.
* @param random tinymt state vector.
*/
static void period_certification(tinymt64_t * random) {
if ((random->status[0] & TINYMT64_MASK) == 0 &&
random->status[1] == 0) {
random->status[0] = 'T';
random->status[1] = 'M';
}
}
/**
* This function initializes the internal state array with a 64-bit
* unsigned integer seed.
* @param random tinymt state vector.
* @param seed a 64-bit unsigned integer used as a seed.
*/
void tinymt64_init(tinymt64_t * random, uint64_t seed) {
random->status[0] = seed ^ ((uint64_t)random->mat1 << 32);
random->status[1] = random->mat2 ^ random->tmat;
for (int i = 1; i < MIN_LOOP; i++) {
random->status[i & 1] ^= i + UINT64_C(6364136223846793005)
* (random->status[(i - 1) & 1]
^ (random->status[(i - 1) & 1] >> 62));
}
period_certification(random);
}
/**
* This function initializes the internal state array,
* with an array of 64-bit unsigned integers used as seeds
* @param random tinymt state vector.
* @param init_key the array of 64-bit integers, used as a seed.
* @param key_length the length of init_key.
*/
void tinymt64_init_by_array(tinymt64_t * random, const uint64_t init_key[],
int key_length) {
const int lag = 1;
const int mid = 1;
const int size = 4;
int i, j;
int count;
uint64_t r;
uint64_t st[4];
st[0] = 0;
st[1] = random->mat1;
st[2] = random->mat2;
st[3] = random->tmat;
if (key_length + 1 > MIN_LOOP) {
count = key_length + 1;
} else {
count = MIN_LOOP;
}
r = ini_func1(st[0] ^ st[mid % size]
^ st[(size - 1) % size]);
st[mid % size] += r;
r += key_length;
st[(mid + lag) % size] += r;
st[0] = r;
count--;
for (i = 1, j = 0; (j < count) && (j < key_length); j++) {
r = ini_func1(st[i] ^ st[(i + mid) % size] ^ st[(i + size - 1) % size]);
st[(i + mid) % size] += r;
r += init_key[j] + i;
st[(i + mid + lag) % size] += r;
st[i] = r;
i = (i + 1) % size;
}
for (; j < count; j++) {
r = ini_func1(st[i] ^ st[(i + mid) % size] ^ st[(i + size - 1) % size]);
st[(i + mid) % size] += r;
r += i;
st[(i + mid + lag) % size] += r;
st[i] = r;
i = (i + 1) % size;
}
for (j = 0; j < size; j++) {
r = ini_func2(st[i] + st[(i + mid) % size] + st[(i + size - 1) % size]);
st[(i + mid) % size] ^= r;
r -= i;
st[(i + mid + lag) % size] ^= r;
st[i] = r;
i = (i + 1) % size;
}
random->status[0] = st[0] ^ st[1];
random->status[1] = st[2] ^ st[3];
period_certification(random);
}
-210
View File
@@ -1,210 +0,0 @@
#ifndef TINYMT64_H
#define TINYMT64_H
/**
* @file tinymt64.h
*
* @brief Tiny Mersenne Twister only 127 bit internal state
*
* @author Mutsuo Saito (Hiroshima University)
* @author Makoto Matsumoto (The University of Tokyo)
*
* Copyright (C) 2011 Mutsuo Saito, Makoto Matsumoto,
* Hiroshima University and The University of Tokyo.
* All rights reserved.
*
* The 3-clause BSD License is applied to this software, see
* LICENSE.txt
*/
#include <stdint.h>
#include <inttypes.h>
#define TINYMT64_MEXP 127
#define TINYMT64_SH0 12
#define TINYMT64_SH1 11
#define TINYMT64_SH8 8
#define TINYMT64_MASK UINT64_C(0x7fffffffffffffff)
#define TINYMT64_MUL (1.0 / 18446744073709551616.0)
/*
* tinymt64 internal state vector and parameters
*/
struct TINYMT64_T {
uint64_t status[2];
uint32_t mat1;
uint32_t mat2;
uint64_t tmat;
};
typedef struct TINYMT64_T tinymt64_t;
void tinymt64_init(tinymt64_t * random, uint64_t seed);
void tinymt64_init_by_array(tinymt64_t * random, const uint64_t init_key[],
int key_length);
#if defined(__GNUC__)
/**
* This function always returns 127
* @param random not used
* @return always 127
*/
inline static int tinymt64_get_mexp(
tinymt64_t * random __attribute__((unused))) {
return TINYMT64_MEXP;
}
#else
inline static int tinymt64_get_mexp(tinymt64_t * random) {
return TINYMT64_MEXP;
}
#endif
/**
* This function changes internal state of tinymt64.
* Users should not call this function directly.
* @param random tinymt internal status
*/
inline static void tinymt64_next_state(tinymt64_t * random) {
uint64_t x;
random->status[0] &= TINYMT64_MASK;
x = random->status[0] ^ random->status[1];
x ^= x << TINYMT64_SH0;
x ^= x >> 32;
x ^= x << 32;
x ^= x << TINYMT64_SH1;
random->status[0] = random->status[1];
random->status[1] = x;
random->status[0] ^= -((int64_t)(x & 1)) & random->mat1;
random->status[1] ^= -((int64_t)(x & 1)) & (((uint64_t)random->mat2) << 32);
}
/**
* This function outputs 64-bit unsigned integer from internal state.
* Users should not call this function directly.
* @param random tinymt internal status
* @return 64-bit unsigned pseudorandom number
*/
inline static uint64_t tinymt64_temper(tinymt64_t * random) {
uint64_t x;
#if defined(LINEARITY_CHECK)
x = random->status[0] ^ random->status[1];
#else
x = random->status[0] + random->status[1];
#endif
x ^= random->status[0] >> TINYMT64_SH8;
x ^= -((int64_t)(x & 1)) & random->tmat;
return x;
}
/**
* This function outputs floating point number from internal state.
* Users should not call this function directly.
* @param random tinymt internal status
* @return floating point number r (1.0 <= r < 2.0)
*/
inline static double tinymt64_temper_conv(tinymt64_t * random) {
uint64_t x;
union {
uint64_t u;
double d;
} conv;
#if defined(LINEARITY_CHECK)
x = random->status[0] ^ random->status[1];
#else
x = random->status[0] + random->status[1];
#endif
x ^= random->status[0] >> TINYMT64_SH8;
conv.u = ((x ^ (-((int64_t)(x & 1)) & random->tmat)) >> 12)
| UINT64_C(0x3ff0000000000000);
return conv.d;
}
/**
* This function outputs floating point number from internal state.
* Users should not call this function directly.
* @param random tinymt internal status
* @return floating point number r (1.0 < r < 2.0)
*/
inline static double tinymt64_temper_conv_open(tinymt64_t * random) {
uint64_t x;
union {
uint64_t u;
double d;
} conv;
#if defined(LINEARITY_CHECK)
x = random->status[0] ^ random->status[1];
#else
x = random->status[0] + random->status[1];
#endif
x ^= random->status[0] >> TINYMT64_SH8;
conv.u = ((x ^ (-((int64_t)(x & 1)) & random->tmat)) >> 12)
| UINT64_C(0x3ff0000000000001);
return conv.d;
}
/**
* This function outputs 64-bit unsigned integer from internal state.
* @param random tinymt internal status
* @return 64-bit unsigned integer r (0 <= r < 2^64)
*/
inline static uint64_t tinymt64_generate_uint64(tinymt64_t * random) {
tinymt64_next_state(random);
return tinymt64_temper(random);
}
/**
* This function outputs floating point number from internal state.
* This function is implemented using multiplying by 1 / 2^64.
* @param random tinymt internal status
* @return floating point number r (0.0 <= r < 1.0)
*/
inline static double tinymt64_generate_double(tinymt64_t * random) {
tinymt64_next_state(random);
return tinymt64_temper(random) * TINYMT64_MUL;
}
/**
* This function outputs floating point number from internal state.
* This function is implemented using union trick.
* @param random tinymt internal status
* @return floating point number r (0.0 <= r < 1.0)
*/
inline static double tinymt64_generate_double01(tinymt64_t * random) {
tinymt64_next_state(random);
return tinymt64_temper_conv(random) - 1.0;
}
/**
* This function outputs floating point number from internal state.
* This function is implemented using union trick.
* @param random tinymt internal status
* @return floating point number r (1.0 <= r < 2.0)
*/
inline static double tinymt64_generate_double12(tinymt64_t * random) {
tinymt64_next_state(random);
return tinymt64_temper_conv(random);
}
/**
* This function outputs floating point number from internal state.
* This function is implemented using union trick.
* @param random tinymt internal status
* @return floating point number r (0.0 < r <= 1.0)
*/
inline static double tinymt64_generate_doubleOC(tinymt64_t * random) {
tinymt64_next_state(random);
return 2.0 - tinymt64_temper_conv(random);
}
/**
* This function outputs floating point number from internal state.
* This function is implemented using union trick.
* @param random tinymt internal status
* @return floating point number r (0.0 < r < 1.0)
*/
inline static double tinymt64_generate_doubleOO(tinymt64_t * random) {
tinymt64_next_state(random);
return tinymt64_temper_conv_open(random) - 1.0;
}
#endif
+65 -155
View File
@@ -1,10 +1,10 @@
// Copyright (C) 2012 - Will Glozer. All rights reserved.
#include "wrk.h"
#include "script.h"
#include "main.h"
static struct config {
struct addrinfo addr;
uint64_t threads;
uint64_t connections;
uint64_t duration;
@@ -19,7 +19,6 @@ static struct config {
static struct {
stats *latency;
stats *requests;
pthread_mutex_t mutex;
} statistics;
static struct sock sock = {
@@ -58,57 +57,18 @@ static void usage() {
}
int main(int argc, char **argv) {
struct addrinfo *addrs, *addr;
struct http_parser_url parser_url;
char *url, **headers;
int rc;
char *url, **headers = zmalloc(argc * sizeof(char *));
struct http_parser_url parts = {};
headers = zmalloc((argc / 2) * sizeof(char *));
if (parse_args(&cfg, &url, headers, argc, argv)) {
if (parse_args(&cfg, &url, &parts, headers, argc, argv)) {
usage();
exit(1);
}
if (http_parser_parse_url(url, strlen(url), 0, &parser_url)) {
fprintf(stderr, "invalid URL: %s\n", url);
exit(1);
}
char *schema = extract_url_part(url, &parser_url, UF_SCHEMA);
char *host = extract_url_part(url, &parser_url, UF_HOST);
char *port = extract_url_part(url, &parser_url, UF_PORT);
char *schema = copy_url_part(url, &parts, UF_SCHEMA);
char *host = copy_url_part(url, &parts, UF_HOST);
char *port = copy_url_part(url, &parts, UF_PORT);
char *service = port ? port : schema;
char *path = "/";
if (parser_url.field_set & (1 << UF_PATH)) {
path = &url[parser_url.field_data[UF_PATH].off];
}
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM
};
if ((rc = getaddrinfo(host, service, &hints, &addrs)) != 0) {
const char *msg = gai_strerror(rc);
fprintf(stderr, "unable to resolve %s:%s %s\n", host, service, msg);
exit(1);
}
for (addr = addrs; addr != NULL; addr = addr->ai_next) {
int fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (fd == -1) continue;
rc = connect(fd, addr->ai_addr, addr->ai_addrlen);
close(fd);
if (rc == 0) break;
}
if (addr == NULL) {
char *msg = strerror(errno);
fprintf(stderr, "unable to connect to %s:%s %s\n", host, service, msg);
exit(1);
}
if (!strncmp("https", schema, 5)) {
if ((cfg.ctx = ssl_init()) == NULL) {
@@ -125,25 +85,25 @@ int main(int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
signal(SIGINT, SIG_IGN);
cfg.addr = *addr;
pthread_mutex_init(&statistics.mutex, NULL);
statistics.latency = stats_alloc(SAMPLES);
statistics.requests = stats_alloc(SAMPLES);
statistics.latency = stats_alloc(cfg.timeout * 1000);
statistics.requests = stats_alloc(MAX_THREAD_RATE_S);
thread *threads = zcalloc(cfg.threads * sizeof(thread));
thread *threads = zcalloc(cfg.threads * sizeof(thread));
uint64_t connections = cfg.connections / cfg.threads;
uint64_t stop_at = time_us() + (cfg.duration * 1000000);
lua_State *L = script_create(cfg.script, url, headers);
if (!script_resolve(L, host, service)) {
char *msg = strerror(errno);
fprintf(stderr, "unable to connect to %s:%s %s\n", host, service, msg);
exit(1);
}
for (uint64_t i = 0; i < cfg.threads; i++) {
thread *t = &threads[i];
thread *t = &threads[i];
t->loop = aeCreateEventLoop(10 + cfg.connections * 3);
t->connections = connections;
t->stop_at = stop_at;
t->connections = cfg.connections / cfg.threads;
t->L = script_create(schema, host, port, path);
script_headers(t->L, headers);
script_init(t->L, cfg.script, argc - optind, &argv[optind]);
t->L = script_create(cfg.script, url, headers);
script_init(L, t, argc - optind, &argv[optind]);
if (i == 0) {
cfg.pipeline = script_verify_request(t->L);
@@ -178,6 +138,9 @@ int main(int argc, char **argv) {
uint64_t bytes = 0;
errors errors = { 0 };
sleep(cfg.duration);
stop = 1;
for (uint64_t i = 0; i < cfg.threads; i++) {
thread *t = &threads[i];
pthread_join(t->thread, NULL);
@@ -197,6 +160,11 @@ int main(int argc, char **argv) {
long double req_per_s = complete / runtime_s;
long double bytes_per_s = bytes / runtime_s;
if (complete / cfg.connections > 0) {
int64_t interval = runtime_us / (complete / cfg.connections);
stats_correct(statistics.latency, interval);
}
print_stats_header();
print_stats("Latency", statistics.latency, format_time_us);
print_stats("Req/Sec", statistics.requests, format_metric);
@@ -217,7 +185,6 @@ int main(int argc, char **argv) {
printf("Requests/sec: %9.2Lf\n", req_per_s);
printf("Transfer/sec: %10sB\n", format_binary(bytes_per_s));
lua_State *L = threads[0].L;
if (script_has_done(L)) {
script_summary(L, runtime_us, complete, bytes);
script_errors(L, &errors);
@@ -229,11 +196,6 @@ int main(int argc, char **argv) {
void *thread_main(void *arg) {
thread *thread = arg;
aeEventLoop *loop = thread->loop;
thread->cs = zcalloc(thread->connections * sizeof(connection));
tinymt64_init(&thread->rand, time_us());
thread->latency = stats_alloc(100000);
char *request = NULL;
size_t length = 0;
@@ -242,6 +204,7 @@ void *thread_main(void *arg) {
script_request(thread->L, &request, &length);
}
thread->cs = zcalloc(thread->connections * sizeof(connection));
connection *c = thread->cs;
for (uint64_t i = 0; i < thread->connections; i++, c++) {
@@ -252,8 +215,8 @@ void *thread_main(void *arg) {
connect_socket(thread, c);
}
aeCreateTimeEvent(loop, CALIBRATE_DELAY_MS, calibrate, thread, NULL);
aeCreateTimeEvent(loop, TIMEOUT_INTERVAL_MS, check_timeouts, thread, NULL);
aeEventLoop *loop = thread->loop;
aeCreateTimeEvent(loop, RECORD_INTERVAL_MS, record_rate, thread, NULL);
thread->start = time_us();
aeMain(loop);
@@ -261,29 +224,20 @@ void *thread_main(void *arg) {
aeDeleteEventLoop(loop);
zfree(thread->cs);
uint64_t max = thread->latency->max;
stats_free(thread->latency);
pthread_mutex_lock(&statistics.mutex);
for (uint64_t i = 0; i < thread->missed; i++) {
stats_record(statistics.latency, max);
}
pthread_mutex_unlock(&statistics.mutex);
return NULL;
}
static int connect_socket(thread *thread, connection *c) {
struct addrinfo addr = cfg.addr;
struct addrinfo *addr = thread->addr;
struct aeEventLoop *loop = thread->loop;
int fd, flags;
fd = socket(addr.ai_family, addr.ai_socktype, addr.ai_protocol);
fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
if (connect(fd, addr.ai_addr, addr.ai_addrlen) == -1) {
if (connect(fd, addr->ai_addr, addr->ai_addrlen) == -1) {
if (errno != EINPROGRESS) goto error;
}
@@ -310,66 +264,22 @@ static int reconnect_socket(thread *thread, connection *c) {
return connect_socket(thread, c);
}
static int calibrate(aeEventLoop *loop, long long id, void *data) {
static int record_rate(aeEventLoop *loop, long long id, void *data) {
thread *thread = data;
(void) stats_summarize(thread->latency);
long double latency = stats_percentile(thread->latency, 90.0) / 1000.0L;
long double interval = MAX(latency * 2, 10);
long double rate = (interval / latency) * thread->connections;
if (thread->requests > 0) {
uint64_t elapsed_ms = (time_us() - thread->start) / 1000;
uint64_t requests = (thread->requests / (double) elapsed_ms) * 1000;
if (latency == 0) return CALIBRATE_DELAY_MS;
stats_record(statistics.requests, requests);
thread->interval = interval;
thread->rate = ceil(rate / 10);
thread->start = time_us();
thread->requests = 0;
stats_reset(thread->latency);
aeCreateTimeEvent(loop, thread->interval, sample_rate, thread, NULL);
return AE_NOMORE;
}
static int check_timeouts(aeEventLoop *loop, long long id, void *data) {
thread *thread = data;
connection *c = thread->cs;
uint64_t now = time_us();
uint64_t maxAge = now - (cfg.timeout * 1000);
for (uint64_t i = 0; i < thread->connections; i++, c++) {
if (maxAge > c->start) {
thread->errors.timeout++;
}
thread->requests = 0;
thread->start = time_us();
}
if (stop || now >= thread->stop_at) {
aeStop(loop);
}
if (stop) aeStop(loop);
return TIMEOUT_INTERVAL_MS;
}
static int sample_rate(aeEventLoop *loop, long long id, void *data) {
thread *thread = data;
uint64_t elapsed_ms = (time_us() - thread->start) / 1000;
uint64_t requests = (thread->requests / (double) elapsed_ms) * 1000;
uint64_t missed = thread->rate - MIN(thread->rate, thread->latency->limit);
uint64_t count = thread->rate - missed;
pthread_mutex_lock(&statistics.mutex);
stats_sample(statistics.latency, &thread->rand, count, thread->latency);
stats_record(statistics.requests, requests);
pthread_mutex_unlock(&statistics.mutex);
thread->missed += missed;
thread->requests = 0;
thread->start = time_us();
stats_rewind(thread->latency);
return thread->interval;
return RECORD_INTERVAL_MS;
}
static int header_field(http_parser *parser, const char *at, size_t len) {
@@ -417,13 +327,10 @@ static int response_complete(http_parser *parser) {
c->state = FIELD;
}
if (now >= thread->stop_at) {
aeStop(thread->loop);
goto done;
}
if (--c->pending == 0) {
stats_record(thread->latency, now - c->start);
if (!stats_record(statistics.latency, now - c->start)) {
thread->errors.timeout++;
}
aeCreateFileEvent(thread->loop, c->fd, AE_WRITABLE, socket_writeable, c);
}
@@ -458,15 +365,18 @@ static void socket_connected(aeEventLoop *loop, int fd, void *data, int mask) {
error:
c->thread->errors.connect++;
reconnect_socket(c->thread, c);
}
static void socket_writeable(aeEventLoop *loop, int fd, void *data, int mask) {
connection *c = data;
thread *thread = c->thread;
if (!c->written && cfg.dynamic) {
script_request(thread->L, &c->request, &c->length);
if (!c->written) {
if (cfg.dynamic) {
script_request(thread->L, &c->request, &c->length);
}
c->start = time_us();
c->pending = cfg.pipeline;
}
char *buf = c->request + c->written;
@@ -479,11 +389,6 @@ static void socket_writeable(aeEventLoop *loop, int fd, void *data, int mask) {
case RETRY: return;
}
if (!c->written) {
c->start = time_us();
c->pending = cfg.pipeline;
}
c->written += n;
if (c->written == c->length) {
c->written = 0;
@@ -497,7 +402,6 @@ static void socket_writeable(aeEventLoop *loop, int fd, void *data, int mask) {
reconnect_socket(thread, c);
}
static void socket_readable(aeEventLoop *loop, int fd, void *data, int mask) {
connection *c = data;
size_t n;
@@ -526,12 +430,12 @@ static uint64_t time_us() {
return (t.tv_sec * 1000000) + t.tv_usec;
}
static char *extract_url_part(char *url, struct http_parser_url *parser_url, enum http_parser_url_fields field) {
static char *copy_url_part(char *url, struct http_parser_url *parts, enum http_parser_url_fields field) {
char *part = NULL;
if (parser_url->field_set & (1 << field)) {
uint16_t off = parser_url->field_data[field].off;
uint16_t len = parser_url->field_data[field].len;
if (parts->field_set & (1 << field)) {
uint16_t off = parts->field_data[field].off;
uint16_t len = parts->field_data[field].len;
part = zcalloc(len + 1 * sizeof(char));
memcpy(part, &url[off], len);
}
@@ -552,8 +456,9 @@ static struct option longopts[] = {
{ NULL, 0, NULL, 0 }
};
static int parse_args(struct config *cfg, char **url, char **headers, int argc, char **argv) {
char c, **header = headers;
static int parse_args(struct config *cfg, char **url, struct http_parser_url *parts, char **headers, int argc, char **argv) {
char **header = headers;
int c;
memset(cfg, 0, sizeof(struct config));
cfg->threads = 2;
@@ -599,6 +504,11 @@ static int parse_args(struct config *cfg, char **url, char **headers, int argc,
if (optind == argc || !cfg->threads || !cfg->duration) return -1;
if (!script_parse_url(argv[optind], parts)) {
fprintf(stderr, "invalid URL: %s\n", argv[optind]);
return -1;
}
if (!cfg->connections || cfg->connections < cfg->threads) {
fprintf(stderr, "number of connections must be >= threads\n");
return -1;
@@ -629,7 +539,7 @@ static void print_units(long double n, char *(*fmt)(long double), int width) {
static void print_stats(char *name, stats *stats, char *(*fmt)(long double)) {
uint64_t max = stats->max;
long double mean = stats_summarize(stats);
long double mean = stats_mean(stats);
long double stdev = stats_stdev(stats, mean);
printf(" %-10s", name);
+13 -11
View File
@@ -5,42 +5,44 @@
#include <pthread.h>
#include <inttypes.h>
#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <lua.h>
#include "stats.h"
#include "ae.h"
#include "script.h"
#include "http_parser.h"
#define VERSION "3.1.1"
#define VERSION "4.0.0"
#define RECVBUF 8192
#define SAMPLES 100000000
#define MAX_THREAD_RATE_S 10000000
#define SOCKET_TIMEOUT_MS 2000
#define CALIBRATE_DELAY_MS 500
#define TIMEOUT_INTERVAL_MS 2000
#define RECORD_INTERVAL_MS 100
typedef struct {
pthread_t thread;
aeEventLoop *loop;
struct addrinfo *addr;
uint64_t connections;
int interval;
uint64_t stop_at;
uint64_t complete;
uint64_t requests;
uint64_t bytes;
uint64_t start;
uint64_t rate;
uint64_t missed;
stats *latency;
tinymt64_t rand;
lua_State *L;
errors errors;
struct connection *cs;
} thread;
typedef struct {
char *buffer;
size_t length;
char *cursor;
} buffer;
typedef struct connection {
thread *thread;
http_parser parser;
+40 -23
View File
@@ -5,9 +5,48 @@ local wrk = {
method = "GET",
path = "/",
headers = {},
body = nil
body = nil,
thread = nil,
}
function wrk.resolve(host, service)
local addrs = wrk.lookup(host, service)
for i = #addrs, 1, -1 do
if not wrk.connect(addrs[i]) then
table.remove(addrs, i)
end
end
wrk.addrs = addrs
end
function wrk.setup(thread)
thread.addr = wrk.addrs[1]
if type(setup) == "function" then
setup(thread)
end
end
function wrk.init(args)
if not wrk.headers["Host"] then
local host = wrk.host
local port = wrk.port
host = host:find(":") and ("[" .. host .. "]") or host
host = port and (host .. ":" .. port) or host
wrk.headers["Host"] = host
end
if type(init) == "function" then
init(args)
end
local req = wrk.format()
wrk.request = function()
return req
end
end
function wrk.format(method, path, headers, body)
local method = method or wrk.method
local path = path or wrk.path
@@ -32,26 +71,4 @@ function wrk.format(method, path, headers, body)
return table.concat(s, "\r\n")
end
function wrk.init(args)
if not wrk.headers["Host"] then
local host = wrk.host
local port = wrk.port
host = host:find(":") and ("[" .. host .. "]") or host
host = port and (host .. ":" .. port) or host
wrk.headers["Host"] = host
end
req = wrk.format()
end
function wrk.request()
return req
end
init = wrk.init
request = wrk.request
response = nil
done = nil
return wrk