Skip to content

Commit

Permalink
call optional done() script function
Browse files Browse the repository at this point in the history
  • Loading branch information
wg committed Aug 25, 2013
1 parent e24ed26 commit 1e7411a
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 43 deletions.
11 changes: 8 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ ifeq ($(TARGET), sunos)
LIBS += -lsocket
else ifeq ($(TARGET), darwin)
LDFLAGS += -pagezero_size 10000 -image_base 100000000
else
else ifeq ($(TARGET), linux)
LIBS += -ldl
LDFLAGS += -Wl,-E
else ifeq ($(TARGET), freebsd)
CFLAGS += -D_DECLARE_C99_LDBL_MATH
LDFLAGS += -Wl,-E
endif

Expand All @@ -20,8 +24,9 @@ ODIR := obj
OBJ := $(patsubst %.c,$(ODIR)/%.o,$(SRC))

LDIR = deps/luajit/src
LIBS := -lluajit $(LIBS)
CFLAGS += -I $(LDIR)
LDFLAGS += -L $(LDIR) -lluajit
LDFLAGS += -L $(LDIR)

all: $(BIN)

Expand All @@ -39,7 +44,7 @@ $(ODIR): $(LDIR)/libluajit.a

$(ODIR)/bytecode.o: scripts/wrk.lua
@echo LUAJIT $<
@$(SHELL) -c 'cd $(LDIR) && luajit -b $(PWD)/$< $(PWD)/$@'
@$(SHELL) -c 'cd $(LDIR) && ./luajit -b $(PWD)/$< $(PWD)/$@'

$(ODIR)/%.o : %.c
@echo CC $<
Expand Down
58 changes: 44 additions & 14 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ wrk - a HTTP benchmarking tool
load when run on a single multi-core CPU. It combines a multithreaded
design with scalable event notification systems such as epoll and kqueue.

This "scripted" branch of wrk includes LuaJIT and a Lua script may be
used to perform minor alterations to the default HTTP request or even
even generate a completely new HTTP request each time. The script may
also perform custom reporting at the end of a run.

Basic Usage

wrk -t12 -c400 -d30s http://127.0.0.1:8080/index.html
Expand All @@ -24,16 +29,11 @@ Basic Usage

Scripting

The "scripted" branch of wrk includes LuaJIT and a Lua script may be
used to perform minor alterations to the default HTTP request or even
even generate a completely new HTTP request each time. Per-request
actions, particularly building a new HTTP request, will necessarily
reduce the amount of load that can be generated.

wrk's public Lua API is:

init = function()
init = function(args)
request = function()
done = function(summary, latency, requests)

wrk = {
scheme = "http",
Expand All @@ -50,13 +50,37 @@ Scripting
wrk.format returns a HTTP request string containing the passed
parameters merged with values from the wrk table.

global init - function to be called when the thread is initialized
global request - function returning the HTTP message for each request

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 generated in the call to init() and returned
via a quick lookup in the request() call.
global init -- function to be called when the thread is initialized
global request -- function returning the HTTP message for each request
global done -- optional function to be 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

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

Expand All @@ -65,6 +89,12 @@ Benchmarking Tips
initial connection burst the server's listen(2) backlog should be greater
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, will necessarily reduce the amount of load that can be generated.

Acknowledgements

wrk contains code from a number of open source projects including the
Expand Down
5 changes: 3 additions & 2 deletions scripts/wrk.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ function wrk.format(method, path, headers, body)
return table.concat(s, "\r\n")
end

function wrk.init() req = wrk.format() end
function wrk.request() return req end
function wrk.init(args) req = wrk.format() end
function wrk.request() return req end

init = wrk.init
request = wrk.request
done = nil

return wrk
164 changes: 152 additions & 12 deletions src/script.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,41 @@
#include <string.h>
#include "script.h"

lua_State *script_create(char *scheme, char *host, int port, char *path) {
typedef struct {
char *name;
int type;
void *value;
} table_field;

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 const struct luaL_reg statslib[] = {
{ "__index", script_stats_get },
{ "__len", script_stats_len },
{ NULL, NULL }
};

lua_State *script_create(char *scheme, char *host, char *port, char *path) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_dostring(L, "wrk = require \"wrk\"");

luaL_newmetatable(L, "wrk.stats");
luaL_register(L, NULL, statslib);
lua_pop(L, 1);

const table_field fields[] = {
{ "scheme", LUA_TSTRING, scheme },
{ "host", LUA_TSTRING, host },
{ "port", LUA_TSTRING, port },
{ "path", LUA_TSTRING, path },
{ NULL, 0, NULL },
};

lua_getglobal(L, "wrk");
lua_pushstring(L, scheme);
lua_pushstring(L, host);
lua_pushinteger(L, port);
lua_pushstring(L, path);
lua_setfield(L, 1, "path");
lua_setfield(L, 1, "port");
lua_setfield(L, 1, "host");
lua_setfield(L, 1, "scheme");
set_fields(L, 1, fields);
lua_pop(L, 1);

return L;
Expand All @@ -36,14 +57,19 @@ void script_headers(lua_State *L, char **headers) {
lua_pop(L, 2);
}

void script_init(lua_State *L, char *script) {
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, "script %s failed: %s", script, cause);
fprintf(stderr, "%s: %s\n", script, cause);
}

lua_getglobal(L, "init");
lua_call(L, 0, 0);
lua_newtable(L);
for (int i = 0; i < argc; i++) {
lua_pushstring(L, argv[i]);
lua_rawseti(L, 2, i);
}
lua_call(L, 1, 0);
}

void script_request(lua_State *L, char **buf, size_t *len) {
Expand All @@ -62,3 +88,117 @@ bool script_is_static(lua_State *L) {
lua_pop(L, 3);
return is_static;
}

bool script_has_done(lua_State *L) {
lua_getglobal(L, "done");
bool has_done = lua_type(L, 1) == LUA_TFUNCTION;
lua_pop(L, 1);
return has_done;
}

void script_summary(lua_State *L, uint64_t duration, uint64_t requests, uint64_t bytes) {
const table_field fields[] = {
{ "duration", LUA_TNUMBER, &duration },
{ "requests", LUA_TNUMBER, &requests },
{ "bytes", LUA_TNUMBER, &bytes },
{ NULL, 0, NULL },
};
lua_newtable(L);
set_fields(L, 1, fields);
}

void script_errors(lua_State *L, errors *errors) {
uint64_t e[] = {
errors->connect,
errors->read,
errors->write,
errors->status,
errors->timeout
};
const table_field fields[] = {
{ "connect", LUA_TNUMBER, &e[0] },
{ "read", LUA_TNUMBER, &e[1] },
{ "write", LUA_TNUMBER, &e[2] },
{ "status", LUA_TNUMBER, &e[3] },
{ "timeout", LUA_TNUMBER, &e[4] },
{ NULL, 0, NULL },
};
lua_newtable(L);
set_fields(L, 2, fields);
lua_setfield(L, 1, "errors");
}

void script_done(lua_State *L, stats *latency, stats *requests) {
stats **s;

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);

lua_call(L, 3, 0);
lua_pop(L, 5);
}

static stats *checkstats(lua_State *L) {
stats **s = luaL_checkudata(L, 1, "wrk.stats");
luaL_argcheck(L, s != NULL, 1, "`stats' expected");
return *s;
}

static int script_stats_percentile(lua_State *L) {
stats *s = checkstats(L);
lua_Number p = luaL_checknumber(L, 2);
lua_pushnumber(L, stats_percentile(s, p));
return 1;
}

static int script_stats_get(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);
}
}
return 1;
}

static int script_stats_len(lua_State *L) {
stats *s = checkstats(L);
lua_pushinteger(L, s->limit);
return 1;
}

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_TNUMBER:
lua_pushinteger(L, *((lua_Integer *) f.value));
break;
case LUA_TSTRING:
lua_pushstring(L, (const char *) f.value);
break;
case LUA_TNIL:
lua_pushnil(L);
break;
}
lua_setfield(L, index, f.name);
}
}
11 changes: 9 additions & 2 deletions src/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include "stats.h"

lua_State *script_create(char *, char *, int, char *);
lua_State *script_create(char *, char *, char *, char *);
void script_headers(lua_State *, char **);
void script_init(lua_State *, char *);

void script_init(lua_State *, char *, int, char **);
void script_done(lua_State *, stats *, stats *);
void script_request(lua_State *, char **, size_t *);

bool script_is_static(lua_State *);
bool script_has_done(lua_State *L);
void script_summary(lua_State *, uint64_t, uint64_t, uint64_t);
void script_errors(lua_State *, errors *);

#endif /* SCRIPT_H */
3 changes: 3 additions & 0 deletions src/stats.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ static int stats_compare(const void *a, const void *b) {

long double stats_summarize(stats *stats) {
qsort(stats->data, stats->limit, sizeof(uint64_t), &stats_compare);
return stats_mean(stats);
}

long double stats_mean(stats *stats) {
if (stats->limit == 0) return 0.0;

uint64_t sum = 0;
Expand Down
9 changes: 9 additions & 0 deletions src/stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))

typedef struct {
uint32_t connect;
uint32_t read;
uint32_t write;
uint32_t status;
uint32_t timeout;
} errors;

typedef struct {
uint64_t samples;
uint64_t index;
Expand All @@ -24,6 +32,7 @@ void stats_rewind(stats *);
void stats_record(stats *, uint64_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);
Expand Down
Loading

0 comments on commit 1e7411a

Please sign in to comment.