You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

296 lines
7.9 KiB

// Copyright (C) 2013 - Will Glozer. All rights reserved.
#include <stdlib.h>
#include <string.h>
#include "script.h"
#include "http_parser.h"
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");
set_fields(L, 1, fields);
lua_pop(L, 1);
return L;
}
void script_headers(lua_State *L, char **headers) {
lua_getglobal(L, "wrk");
lua_getfield(L, 1, "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_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);
}
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_call(L, 1, 0);
}
void script_request(lua_State *L, char **buf, size_t *len) {
lua_getglobal(L, "request");
lua_call(L, 0, 1);
*buf = (char *) lua_tolstring(L, 1, len);
lua_pop(L, 1);
}
void script_response(lua_State *L, int status, buffer *headers, buffer *body) {
lua_getglobal(L, "response");
lua_pushinteger(L, status);
lua_newtable(L);
for (char *c = headers->buffer; c < headers->cursor; ) {
c = buffer_pushlstring(L, c);
c = buffer_pushlstring(L, c);
lua_rawset(L, -3);
}
lua_pushlstring(L, body->buffer, body->cursor - body->buffer);
lua_call(L, 3, 0);
buffer_reset(headers);
buffer_reset(body);
}
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;
}
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;
}
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;
}
void script_header_done(lua_State *L, luaL_Buffer *buffer) {
luaL_pushresult(buffer);
}
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, 1);
}
static int verify_request(http_parser *parser) {
size_t *count = parser->data;
(*count)++;
return 0;
}
size_t script_verify_request(lua_State *L) {
http_parser_settings settings = {
.on_message_complete = verify_request
};
http_parser parser;
char *request;
size_t len, count = 0;
script_request(L, &request, &len);
http_parser_init(&parser, HTTP_REQUEST);
parser.data = &count;
size_t parsed = http_parser_execute(&parser, &settings, request, len);
if (parsed != len || count == 0) {
enum http_errno err = HTTP_PARSER_ERRNO(&parser);
const char *desc = http_errno_description(err);
const char *msg = err != HPE_OK ? desc : "incomplete request";
int line = 1, column = 1;
for (char *c = request; c < request + parsed; c++) {
column++;
if (*c == '\n') {
column = 1;
line++;
}
}
fprintf(stderr, "%s at %d:%d\n", msg, line, column);
exit(1);
}
return count;
}
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);
}
}
void buffer_append(buffer *b, const char *data, size_t len) {
size_t used = b->cursor - b->buffer;
while (used + len + 1 >= b->length) {
b->length += 1024;
b->buffer = realloc(b->buffer, b->length);
b->cursor = b->buffer + used;
}
memcpy(b->cursor, data, len);
b->cursor += len;
}
void buffer_reset(buffer *b) {
b->cursor = b->buffer;
}
char *buffer_pushlstring(lua_State *L, char *start) {
char *end = strchr(start, 0);
lua_pushlstring(L, start, end - start);
return end + 1;
}