mirror of
https://github.com/wg/wrk
synced 2026-06-09 16:43:42 +08:00
Compare commits
10 Commits
+13
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
+3
-7
@@ -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,9 +1,9 @@
|
||||
#ifndef NET_H
|
||||
#define NET_H
|
||||
|
||||
#include "config.h"
|
||||
#include <stdint.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include "wrk.h"
|
||||
|
||||
typedef enum {
|
||||
|
||||
+338
-75
@@ -4,6 +4,7 @@
|
||||
#include <string.h>
|
||||
#include "script.h"
|
||||
#include "http_parser.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
@@ -11,76 +12,149 @@ 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 index, const table_field *);
|
||||
static void set_string(lua_State *, int, char *, char *, size_t);
|
||||
static char *get_url_part(char *, struct http_parser_url *, enum http_parser_url_fields, size_t *);
|
||||
|
||||
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 = "/";
|
||||
size_t len = 0;
|
||||
|
||||
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_string(L, 4, "scheme", get_url_part(url, &parts, UF_SCHEMA, &len), len);
|
||||
set_string(L, 4, "host", get_url_part(url, &parts, UF_HOST, &len), len);
|
||||
set_string(L, 4, "port", get_url_part(url, &parts, UF_PORT, &len), len);
|
||||
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 +175,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 +230,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 +289,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 +344,187 @@ 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 char *get_url_part(char *url, struct http_parser_url *parts, enum http_parser_url_fields field, size_t *len) {
|
||||
char *value = NULL;
|
||||
if (parts->field_set & (1 << field)) {
|
||||
value = &url[parts->field_data[field].off];
|
||||
*len = parts->field_data[field].len;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static void set_string(lua_State *L, int index, char *field, char *value, size_t len) {
|
||||
if (value != NULL) {
|
||||
lua_pushlstring(L, value, len);
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -495,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;
|
||||
@@ -524,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);
|
||||
}
|
||||
@@ -550,7 +456,7 @@ static struct option longopts[] = {
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
static int parse_args(struct config *cfg, char **url, char **headers, int argc, char **argv) {
|
||||
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;
|
||||
|
||||
@@ -598,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;
|
||||
@@ -628,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);
|
||||
|
||||
@@ -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.2"
|
||||
#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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user