1
0
mirror of https://github.com/wg/wrk synced 2025-02-09 11:32:52 +08:00

add script setup() and thread methods

This commit is contained in:
Will 2015-02-07 17:03:17 +09:00
parent 6f0aa32ede
commit 9b84d3e1a4
14 changed files with 386 additions and 153 deletions

View File

@ -1,4 +1,8 @@
master
wrk next
* 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(key, value), thread:get(key), and thread:stop().

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)

71
README
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,65 +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.
The following globals are optional, and if defined must be functions:
global init -- called when the thread is initialized
global request -- returning the HTTP message for each request
global response -- called with HTTP response data
global done -- called with results of run
The init() function receives any extra command line arguments for the
script which must be separated from wrk arguments with "--".
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
@ -93,11 +34,9 @@ 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

112
SCRIPTING Normal file
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(key) - get the value of a global in the thread's env
thread:set(key, 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 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
}
}

22
scripts/addr.lua Normal file
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

38
scripts/setup.lua Normal file
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
scripts/stop.lua Normal file
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

View File

@ -5,7 +5,6 @@
#define HAVE_KQUEUE
#elif defined(__linux__)
#define HAVE_EPOLL
#define _POSIX_C_SOURCE 200809L
#elif defined (__sun)
#define HAVE_EVPORT
#endif

View File

@ -16,7 +16,6 @@
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/uio.h>

View File

@ -15,9 +15,12 @@ typedef struct {
static int script_addr_tostring(lua_State *);
static int script_addr_gc(lua_State *);
static int script_stats_len(lua_State *);
static int script_stats_get(lua_State *);
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 void set_fields(lua_State *, int index, const table_field *);
static const struct luaL_reg addrlib[] = {
@ -27,23 +30,28 @@ static const struct luaL_reg addrlib[] = {
};
static const struct luaL_reg statslib[] = {
{ "__index", script_stats_get },
{ "__index", script_stats_index },
{ "__len", script_stats_len },
{ NULL, NULL }
};
lua_State *script_create(char *scheme, char *host, char *port, char *path) {
static const struct luaL_reg threadlib[] = {
{ "__index", script_thread_index },
{ "__newindex", script_thread_newindex },
{ NULL, NULL }
};
lua_State *script_create(char *file, char *scheme, char *host, char *port, char *path) {
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);
lua_pop(L, 1);
luaL_newmetatable(L, "wrk.stats");
luaL_register(L, NULL, statslib);
lua_pop(L, 1);
luaL_newmetatable(L, "wrk.thread");
luaL_register(L, NULL, threadlib);
const table_field fields[] = {
{ "scheme", LUA_TSTRING, scheme },
@ -56,19 +64,17 @@ lua_State *script_create(char *scheme, char *host, char *port, char *path) {
};
lua_getglobal(L, "wrk");
set_fields(L, 1, fields);
lua_pop(L, 1);
set_fields(L, 4, fields);
lua_pop(L, 4);
if (file && luaL_dofile(L, file)) {
const char *cause = lua_tostring(L, -1);
fprintf(stderr, "%s: %s\n", file, cause);
}
return L;
}
void script_prepare_setup(lua_State *L, char *script) {
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");
@ -83,13 +89,24 @@ bool script_resolve(lua_State *L, char *host, char *service) {
return count > 0;
}
struct addrinfo *script_peek_addr(lua_State *L) {
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_setup(lua_State *L, thread *t) {
lua_getglobal(t->L, "wrk");
script_push_thread(t->L, t);
lua_setfield(t->L, -2, "thread");
lua_pop(t->L, 1);
lua_getglobal(L, "wrk");
lua_getfield(L, -1, "addrs");
lua_rawgeti(L, -1, 1);
struct addrinfo *addr = lua_touserdata(L, -1);
lua_pop(L, 3);
return addr;
lua_getfield(L, -1, "setup");
script_push_thread(L, t);
lua_call(L, 1, 0);
lua_pop(L, 1);
}
void script_headers(lua_State *L, char **headers) {
@ -106,12 +123,7 @@ void script_headers(lua_State *L, char **headers) {
lua_pop(L, 2);
}
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);
}
void script_init(lua_State *L, int argc, char **argv) {
lua_getglobal(L, "wrk");
lua_getfield(L, -1, "init");
lua_newtable(L);
@ -211,21 +223,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);
@ -278,6 +288,20 @@ static struct addrinfo *checkaddr(lua_State *L) {
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];
@ -313,7 +337,7 @@ static int script_stats_percentile(lua_State *L) {
return 1;
}
static int script_stats_get(lua_State *L) {
static int script_stats_index(lua_State *L) {
stats *s = checkstats(L);
if (lua_isnumber(L, 2)) {
int index = luaL_checkint(L, 2);
@ -337,6 +361,59 @@ static int script_stats_len(lua_State *L) {
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 *key = lua_tostring(L, -2);
script_copy_value(L, t->L, -1);
lua_setglobal(t->L, key);
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 = {
@ -356,13 +433,7 @@ static int script_wrk_lookup(lua_State *L) {
lua_newtable(L);
for (struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next) {
struct addrinfo *udata = lua_newuserdata(L, sizeof(*udata));
luaL_getmetatable(L, "wrk.addr");
lua_setmetatable(L, -2);
*udata = *addr;
udata->ai_addr = zmalloc(addr->ai_addrlen);
memcpy(udata->ai_addr, addr->ai_addr, addr->ai_addrlen);
script_addr_clone(L, addr);
lua_rawseti(L, -2, index++);
}
@ -381,6 +452,36 @@ static int script_wrk_connect(lua_State *L) {
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));
}
}
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];

View File

@ -5,28 +5,21 @@
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <sys/types.h>
#include <netdb.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 *, char *, char *);
lua_State *script_create(char *, char *, char *, char *);
void script_prepare_setup(lua_State *, char *);
bool script_resolve(lua_State *, char *, char *);
struct addrinfo *script_peek_addr(lua_State *);
void script_headers(lua_State *, char **);
size_t script_verify_request(lua_State *L);
void script_init(lua_State *, char *, int, char **);
void script_setup(lua_State *, thread *);
void script_done(lua_State *, stats *, stats *);
void script_headers(lua_State *, char **);
void script_init(lua_State *, 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);
@ -34,6 +27,8 @@ 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);
void buffer_append(buffer *, const char *, size_t);
void buffer_reset(buffer *);
char *buffer_pushlstring(lua_State *, char *);

View File

@ -1,6 +1,7 @@
// Copyright (C) 2012 - Will Glozer. All rights reserved.
#include "wrk.h"
#include "script.h"
#include "main.h"
static struct config {
@ -82,14 +83,6 @@ int main(int argc, char **argv) {
path = &url[parser_url.field_data[UF_PATH].off];
}
lua_State *L = script_create(schema, host, port, path);
script_prepare_setup(L, cfg.script);
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);
}
if (!strncmp("https", schema, 5)) {
if ((cfg.ctx = ssl_init()) == NULL) {
fprintf(stderr, "unable to initialize SSL\n");
@ -111,19 +104,25 @@ int main(int argc, char **argv) {
statistics.requests = stats_alloc(SAMPLES);
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, schema, host, port, path);
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];
t->loop = aeCreateEventLoop(10 + cfg.connections * 3);
t->addr = script_peek_addr(L);
t->connections = connections;
t->connections = cfg.connections / cfg.threads;
t->stop_at = stop_at;
t->L = script_create(schema, host, port, path);
t->L = script_create(cfg.script, schema, host, port, path);
script_headers(t->L, headers);
script_init(t->L, cfg.script, argc - optind, &argv[optind]);
script_setup(L, t);
script_init(t->L, argc - optind, &argv[optind]);
if (i == 0) {
cfg.pipeline = script_verify_request(t->L);
@ -474,7 +473,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;

View File

@ -6,13 +6,14 @@
#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.2"
@ -43,6 +44,12 @@ typedef struct {
struct connection *cs;
} thread;
typedef struct {
char *buffer;
size_t length;
char *cursor;
} buffer;
typedef struct connection {
thread *thread;
http_parser parser;

View File

@ -5,7 +5,8 @@ local wrk = {
method = "GET",
path = "/",
headers = {},
body = nil
body = nil,
thread = nil,
}
function wrk.resolve(host, service)
@ -18,6 +19,13 @@ function wrk.resolve(host, service)
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