1
0
mirror of https://github.com/wg/wrk synced 2026-06-10 00:55:51 +08:00

8 Commits

8 changed files with 422 additions and 274 deletions
+3 -2
View File
@@ -1,10 +1,11 @@
CFLAGS := -std=c99 -Wall -O2 CFLAGS := -std=c99 -Wall -O2 -D_REENTRANT
LIBS := -lpthread -lm LIBS := -lpthread -lm
TARGET := $(shell uname -s | tr [A-Z] [a-z] 2>/dev/null || echo unknown) TARGET := $(shell uname -s | tr [A-Z] [a-z] 2>/dev/null || echo unknown)
ifeq ($(TARGET), sunos) ifeq ($(TARGET), sunos)
LIBS += -lsocket CFLAGS += -D_PTHREADS -D_POSIX_C_SOURCE=200112L
LIBS += -lsocket
endif endif
SRC := wrk.c aprintf.c stats.c units.c ae.c zmalloc.c http_parser.c tinymt64.c SRC := wrk.c aprintf.c stats.c units.c ae.c zmalloc.c http_parser.c tinymt64.c
+4 -4
View File
@@ -6,14 +6,14 @@ wrk - a HTTP benchmarking tool
Basic Usage Basic Usage
wrk -t8 -c400 -r10m http://localhost:8080/index.html wrk -t8 -c400 -d30 http://localhost:8080/index.html
This runs wrk with 8 threads, keeping 400 connections open, and making a This runs a benchmark for 30 seconds, using 8 threads, and keeping
total of 10 million HTTP GET requests to http://localhost:8080/index.html 400 HTTP connections open.
Output: Output:
Making 10000000 requests to http://localhost:8080/index.html Running 30s test @ http://localhost:8080/index.html
8 threads and 400 connections 8 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev Thread Stats Avg Stdev Max +/- Stdev
Latency 439.75us 350.49us 7.60ms 92.88% Latency 439.75us 350.49us 7.60ms 92.88%
+286 -169
View File
@@ -37,19 +37,24 @@
# define MIN(a,b) ((a) < (b) ? (a) : (b)) # define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif #endif
#ifndef ARRAY_SIZE
# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#endif
#ifndef BIT_AT
# define BIT_AT(a, i) \
(!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \
(1 << ((unsigned int) (i) & 7))))
#endif
#ifndef ELEM_AT
# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))
#endif
#if HTTP_PARSER_DEBUG
#define SET_ERRNO(e) \
do { \
parser->http_errno = (e); \
parser->error_lineno = __LINE__; \
} while (0)
#else
#define SET_ERRNO(e) \ #define SET_ERRNO(e) \
do { \ do { \
parser->http_errno = (e); \ parser->http_errno = (e); \
} while(0) } while(0)
#endif
/* Run the notify callback FOR, returning ER if it fails */ /* Run the notify callback FOR, returning ER if it fails */
@@ -123,31 +128,10 @@ do { \
static const char *method_strings[] = static const char *method_strings[] =
{ "DELETE" {
, "GET" #define XX(num, name, string) #string,
, "HEAD" HTTP_METHOD_MAP(XX)
, "POST" #undef XX
, "PUT"
, "CONNECT"
, "OPTIONS"
, "TRACE"
, "COPY"
, "LOCK"
, "MKCOL"
, "MOVE"
, "PROPFIND"
, "PROPPATCH"
, "UNLOCK"
, "REPORT"
, "MKACTIVITY"
, "CHECKOUT"
, "MERGE"
, "M-SEARCH"
, "NOTIFY"
, "SUBSCRIBE"
, "UNSUBSCRIBE"
, "PATCH"
, "PURGE"
}; };
@@ -205,41 +189,49 @@ static const int8_t unhex[256] =
}; };
static const uint8_t normal_url_char[256] = { #if HTTP_PARSER_STRICT
/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ # define T(v) 0
0, 0, 0, 0, 0, 0, 0, 0, #else
/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ # define T(v) v
0, 0, 0, 0, 0, 0, 0, 0, #endif
/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
0, 0, 0, 0, 0, 0, 0, 0,
/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
0, 0, 0, 0, 0, 0, 0, 0,
/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
0, 1, 1, 0, 1, 1, 1, 1,
/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
1, 1, 1, 1, 1, 1, 1, 1,
/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
1, 1, 1, 1, 1, 1, 1, 1,
/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
1, 1, 1, 1, 1, 1, 1, 0,
/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
1, 1, 1, 1, 1, 1, 1, 1,
/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
1, 1, 1, 1, 1, 1, 1, 1,
/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
1, 1, 1, 1, 1, 1, 1, 1,
/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
1, 1, 1, 1, 1, 1, 1, 1,
/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
1, 1, 1, 1, 1, 1, 1, 1,
/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
1, 1, 1, 1, 1, 1, 1, 1,
/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
1, 1, 1, 1, 1, 1, 1, 1,
/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
1, 1, 1, 1, 1, 1, 1, 0, };
static const uint8_t normal_url_char[32] = {
/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0,
/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
0 | 2 | 4 | 0 | 16 | 32 | 64 | 128,
/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,
/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, };
#undef T
enum state enum state
{ s_dead = 1 /* important that this is > 0 */ { s_dead = 1 /* important that this is > 0 */
@@ -266,13 +258,9 @@ enum state
, s_req_schema , s_req_schema
, s_req_schema_slash , s_req_schema_slash
, s_req_schema_slash_slash , s_req_schema_slash_slash
, s_req_host_start , s_req_server_start
, s_req_host_v6_start , s_req_server
, s_req_host_v6 , s_req_server_with_at
, s_req_host_v6_end
, s_req_host
, s_req_port_start
, s_req_port
, s_req_path , s_req_path
, s_req_query_string_start , s_req_query_string_start
, s_req_query_string , s_req_query_string
@@ -350,6 +338,19 @@ enum header_states
, h_connection_close , h_connection_close
}; };
enum http_host_state
{
s_http_host_dead = 1
, s_http_userinfo_start
, s_http_userinfo
, s_http_host_start
, s_http_host_v6_start
, s_http_host
, s_http_host_v6
, s_http_host_v6_end
, s_http_host_port_start
, s_http_host_port
};
/* Macros for character classes; depends on strict-mode */ /* Macros for character classes; depends on strict-mode */
#define CR '\r' #define CR '\r'
@@ -359,15 +360,21 @@ enum header_states
#define IS_NUM(c) ((c) >= '0' && (c) <= '9') #define IS_NUM(c) ((c) >= '0' && (c) <= '9')
#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c))
#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) #define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \
(c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
(c) == ')')
#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
(c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
(c) == '$' || (c) == ',')
#if HTTP_PARSER_STRICT #if HTTP_PARSER_STRICT
#define TOKEN(c) (tokens[(unsigned char)c]) #define TOKEN(c) (tokens[(unsigned char)c])
#define IS_URL_CHAR(c) (normal_url_char[(unsigned char) (c)]) #define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c))
#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') #define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
#else #else
#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) #define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c])
#define IS_URL_CHAR(c) \ #define IS_URL_CHAR(c) \
(normal_url_char[(unsigned char) (c)] || ((c) & 0x80)) (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
#define IS_HOST_CHAR(c) \ #define IS_HOST_CHAR(c) \
(IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
#endif #endif
@@ -401,7 +408,7 @@ static struct {
}; };
#undef HTTP_STRERROR_GEN #undef HTTP_STRERROR_GEN
int http_message_needs_eof(http_parser *parser); int http_message_needs_eof(const http_parser *parser);
/* Our URL parser. /* Our URL parser.
* *
@@ -417,7 +424,15 @@ int http_message_needs_eof(http_parser *parser);
static enum state static enum state
parse_url_char(enum state s, const char ch) parse_url_char(enum state s, const char ch)
{ {
assert(!isspace(ch)); if (ch == ' ' || ch == '\r' || ch == '\n') {
return s_dead;
}
#if HTTP_PARSER_STRICT
if (ch == '\t' || ch == '\f') {
return s_dead;
}
#endif
switch (s) { switch (s) {
case s_req_spaces_before_url: case s_req_spaces_before_url:
@@ -455,67 +470,33 @@ parse_url_char(enum state s, const char ch)
case s_req_schema_slash_slash: case s_req_schema_slash_slash:
if (ch == '/') { if (ch == '/') {
return s_req_host_start; return s_req_server_start;
} }
break; break;
case s_req_host_start: case s_req_server_with_at:
if (ch == '[') { if (ch == '@') {
return s_req_host_v6_start; return s_dead;
} }
if (IS_HOST_CHAR(ch)) { /* FALLTHROUGH */
return s_req_host; case s_req_server_start:
case s_req_server:
if (ch == '/') {
return s_req_path;
} }
break; if (ch == '?') {
return s_req_query_string_start;
case s_req_host:
if (IS_HOST_CHAR(ch)) {
return s_req_host;
} }
/* FALLTHROUGH */ if (ch == '@') {
case s_req_host_v6_end: return s_req_server_with_at;
switch (ch) {
case ':':
return s_req_port_start;
case '/':
return s_req_path;
case '?':
return s_req_query_string_start;
} }
break; if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
return s_req_server;
case s_req_host_v6:
if (ch == ']') {
return s_req_host_v6_end;
}
/* FALLTHROUGH */
case s_req_host_v6_start:
if (IS_HEX(ch) || ch == ':') {
return s_req_host_v6;
}
break;
case s_req_port:
switch (ch) {
case '/':
return s_req_path;
case '?':
return s_req_query_string_start;
}
/* FALLTHROUGH */
case s_req_port_start:
if (IS_NUM(ch)) {
return s_req_port;
} }
break; break;
@@ -637,13 +618,9 @@ size_t http_parser_execute (http_parser *parser,
case s_req_schema: case s_req_schema:
case s_req_schema_slash: case s_req_schema_slash:
case s_req_schema_slash_slash: case s_req_schema_slash_slash:
case s_req_host_start: case s_req_server_start:
case s_req_host_v6_start: case s_req_server:
case s_req_host_v6: case s_req_server_with_at:
case s_req_host_v6_end:
case s_req_host:
case s_req_port_start:
case s_req_port:
case s_req_query_string_start: case s_req_query_string_start:
case s_req_query_string: case s_req_query_string:
case s_req_fragment_start: case s_req_fragment_start:
@@ -889,6 +866,7 @@ size_t http_parser_execute (http_parser *parser,
case s_res_line_almost_done: case s_res_line_almost_done:
STRICT_CHECK(ch != LF); STRICT_CHECK(ch != LF);
parser->state = s_header_field_start; parser->state = s_header_field_start;
CALLBACK_NOTIFY(status_complete);
break; break;
case s_start_req: case s_start_req:
@@ -918,7 +896,7 @@ size_t http_parser_execute (http_parser *parser,
/* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
break; break;
case 'R': parser->method = HTTP_REPORT; break; case 'R': parser->method = HTTP_REPORT; break;
case 'S': parser->method = HTTP_SUBSCRIBE; break; case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break;
case 'T': parser->method = HTTP_TRACE; break; case 'T': parser->method = HTTP_TRACE; break;
case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;
default: default:
@@ -965,6 +943,12 @@ size_t http_parser_execute (http_parser *parser,
} else { } else {
goto error; goto error;
} }
} else if (parser->method == HTTP_SUBSCRIBE) {
if (parser->index == 1 && ch == 'E') {
parser->method = HTTP_SEARCH;
} else {
goto error;
}
} else if (parser->index == 1 && parser->method == HTTP_POST) { } else if (parser->index == 1 && parser->method == HTTP_POST) {
if (ch == 'R') { if (ch == 'R') {
parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
@@ -998,7 +982,7 @@ size_t http_parser_execute (http_parser *parser,
MARK(url); MARK(url);
if (parser->method == HTTP_CONNECT) { if (parser->method == HTTP_CONNECT) {
parser->state = s_req_host_start; parser->state = s_req_server_start;
} }
parser->state = parse_url_char((enum state)parser->state, ch); parser->state = parse_url_char((enum state)parser->state, ch);
@@ -1013,10 +997,7 @@ size_t http_parser_execute (http_parser *parser,
case s_req_schema: case s_req_schema:
case s_req_schema_slash: case s_req_schema_slash:
case s_req_schema_slash_slash: case s_req_schema_slash_slash:
case s_req_host_start: case s_req_server_start:
case s_req_host_v6_start:
case s_req_host_v6:
case s_req_port_start:
{ {
switch (ch) { switch (ch) {
/* No whitespace allowed here */ /* No whitespace allowed here */
@@ -1036,9 +1017,8 @@ size_t http_parser_execute (http_parser *parser,
break; break;
} }
case s_req_host: case s_req_server:
case s_req_host_v6_end: case s_req_server_with_at:
case s_req_port:
case s_req_path: case s_req_path:
case s_req_query_string_start: case s_req_query_string_start:
case s_req_query_string: case s_req_query_string:
@@ -1867,7 +1847,7 @@ error:
/* Does the parser need to see an EOF to find the end of the message? */ /* Does the parser need to see an EOF to find the end of the message? */
int int
http_message_needs_eof (http_parser *parser) http_message_needs_eof (const http_parser *parser)
{ {
if (parser->type == HTTP_REQUEST) { if (parser->type == HTTP_REQUEST) {
return 0; return 0;
@@ -1890,7 +1870,7 @@ http_message_needs_eof (http_parser *parser)
int int
http_should_keep_alive (http_parser *parser) http_should_keep_alive (const http_parser *parser)
{ {
if (parser->http_major > 0 && parser->http_minor > 0) { if (parser->http_major > 0 && parser->http_minor > 0) {
/* HTTP/1.1 */ /* HTTP/1.1 */
@@ -1908,9 +1888,10 @@ http_should_keep_alive (http_parser *parser)
} }
const char * http_method_str (enum http_method m) const char *
http_method_str (enum http_method m)
{ {
return method_strings[m]; return ELEM_AT(method_strings, m, "<unknown>");
} }
@@ -1937,6 +1918,144 @@ http_errno_description(enum http_errno err) {
return http_strerror_tab[err].description; return http_strerror_tab[err].description;
} }
static enum http_host_state
http_parse_host_char(enum http_host_state s, const char ch) {
switch(s) {
case s_http_userinfo:
case s_http_userinfo_start:
if (ch == '@') {
return s_http_host_start;
}
if (IS_USERINFO_CHAR(ch)) {
return s_http_userinfo;
}
break;
case s_http_host_start:
if (ch == '[') {
return s_http_host_v6_start;
}
if (IS_HOST_CHAR(ch)) {
return s_http_host;
}
break;
case s_http_host:
if (IS_HOST_CHAR(ch)) {
return s_http_host;
}
/* FALLTHROUGH */
case s_http_host_v6_end:
if (ch == ':') {
return s_http_host_port_start;
}
break;
case s_http_host_v6:
if (ch == ']') {
return s_http_host_v6_end;
}
/* FALLTHROUGH */
case s_http_host_v6_start:
if (IS_HEX(ch) || ch == ':' || ch == '.') {
return s_http_host_v6;
}
break;
case s_http_host_port:
case s_http_host_port_start:
if (IS_NUM(ch)) {
return s_http_host_port;
}
break;
default:
break;
}
return s_http_host_dead;
}
static int
http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
enum http_host_state s;
const char *p;
size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
u->field_data[UF_HOST].len = 0;
s = found_at ? s_http_userinfo_start : s_http_host_start;
for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {
enum http_host_state new_s = http_parse_host_char(s, *p);
if (new_s == s_http_host_dead) {
return 1;
}
switch(new_s) {
case s_http_host:
if (s != s_http_host) {
u->field_data[UF_HOST].off = p - buf;
}
u->field_data[UF_HOST].len++;
break;
case s_http_host_v6:
if (s != s_http_host_v6) {
u->field_data[UF_HOST].off = p - buf;
}
u->field_data[UF_HOST].len++;
break;
case s_http_host_port:
if (s != s_http_host_port) {
u->field_data[UF_PORT].off = p - buf;
u->field_data[UF_PORT].len = 0;
u->field_set |= (1 << UF_PORT);
}
u->field_data[UF_PORT].len++;
break;
case s_http_userinfo:
if (s != s_http_userinfo) {
u->field_data[UF_USERINFO].off = p - buf ;
u->field_data[UF_USERINFO].len = 0;
u->field_set |= (1 << UF_USERINFO);
}
u->field_data[UF_USERINFO].len++;
break;
default:
break;
}
s = new_s;
}
/* Make sure we don't end somewhere unexpected */
switch (s) {
case s_http_host_start:
case s_http_host_v6_start:
case s_http_host_v6:
case s_http_host_port_start:
case s_http_userinfo:
case s_http_userinfo_start:
return 1;
default:
break;
}
return 0;
}
int int
http_parser_parse_url(const char *buf, size_t buflen, int is_connect, http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
struct http_parser_url *u) struct http_parser_url *u)
@@ -1944,9 +2063,10 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
enum state s; enum state s;
const char *p; const char *p;
enum http_parser_url_fields uf, old_uf; enum http_parser_url_fields uf, old_uf;
int found_at = 0;
u->port = u->field_set = 0; u->port = u->field_set = 0;
s = is_connect ? s_req_host_start : s_req_spaces_before_url; s = is_connect ? s_req_server_start : s_req_spaces_before_url;
uf = old_uf = UF_MAX; uf = old_uf = UF_MAX;
for (p = buf; p < buf + buflen; p++) { for (p = buf; p < buf + buflen; p++) {
@@ -1960,10 +2080,7 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
/* Skip delimeters */ /* Skip delimeters */
case s_req_schema_slash: case s_req_schema_slash:
case s_req_schema_slash_slash: case s_req_schema_slash_slash:
case s_req_host_start: case s_req_server_start:
case s_req_host_v6_start:
case s_req_host_v6_end:
case s_req_port_start:
case s_req_query_string_start: case s_req_query_string_start:
case s_req_fragment_start: case s_req_fragment_start:
continue; continue;
@@ -1972,13 +2089,12 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
uf = UF_SCHEMA; uf = UF_SCHEMA;
break; break;
case s_req_host: case s_req_server_with_at:
case s_req_host_v6: found_at = 1;
uf = UF_HOST;
break;
case s_req_port: /* FALLTROUGH */
uf = UF_PORT; case s_req_server:
uf = UF_HOST;
break; break;
case s_req_path: case s_req_path:
@@ -2011,23 +2127,19 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
old_uf = uf; old_uf = uf;
} }
/* host must be present if there is a schema */
/* parsing http:///toto will fail */
if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) {
if (http_parse_host(buf, u, found_at) != 0) {
return 1;
}
}
/* CONNECT requests can only contain "hostname:port" */ /* CONNECT requests can only contain "hostname:port" */
if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
return 1; return 1;
} }
/* Make sure we don't end somewhere unexpected */
switch (s) {
case s_req_host_v6_start:
case s_req_host_v6:
case s_req_host_v6_end:
case s_req_host:
case s_req_port_start:
return 1;
default:
break;
}
if (u->field_set & (1 << UF_PORT)) { if (u->field_set & (1 << UF_PORT)) {
/* Don't bother with endp; we've already validated the string */ /* Don't bother with endp; we've already validated the string */
unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10);
@@ -2056,3 +2168,8 @@ http_parser_pause(http_parser *parser, int paused) {
assert(0 && "Attempting to pause parser in error state"); assert(0 && "Attempting to pause parser in error state");
} }
} }
int
http_body_is_final(const struct http_parser *parser) {
return parser->state == s_message_done;
}
+48 -61
View File
@@ -24,11 +24,13 @@
extern "C" { extern "C" {
#endif #endif
#define HTTP_PARSER_VERSION_MAJOR 1 #define HTTP_PARSER_VERSION_MAJOR 2
#define HTTP_PARSER_VERSION_MINOR 0 #define HTTP_PARSER_VERSION_MINOR 1
#include <sys/types.h> #include <sys/types.h>
#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) #if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)
#include <BaseTsd.h>
#include <stddef.h>
typedef __int8 int8_t; typedef __int8 int8_t;
typedef unsigned __int8 uint8_t; typedef unsigned __int8 uint8_t;
typedef __int16 int16_t; typedef __int16 int16_t;
@@ -37,9 +39,6 @@ typedef __int32 int32_t;
typedef unsigned __int32 uint32_t; typedef unsigned __int32 uint32_t;
typedef __int64 int64_t; typedef __int64 int64_t;
typedef unsigned __int64 uint64_t; typedef unsigned __int64 uint64_t;
typedef unsigned int size_t;
typedef int ssize_t;
#else #else
#include <stdint.h> #include <stdint.h>
#endif #endif
@@ -51,14 +50,6 @@ typedef int ssize_t;
# define HTTP_PARSER_STRICT 1 # define HTTP_PARSER_STRICT 1
#endif #endif
/* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to
* the error reporting facility.
*/
#ifndef HTTP_PARSER_DEBUG
# define HTTP_PARSER_DEBUG 0
#endif
/* Maximium header size allowed */ /* Maximium header size allowed */
#define HTTP_MAX_HEADER_SIZE (80*1024) #define HTTP_MAX_HEADER_SIZE (80*1024)
@@ -77,7 +68,7 @@ typedef struct http_parser_settings http_parser_settings;
* chunked' headers that indicate the presence of a body. * chunked' headers that indicate the presence of a body.
* *
* http_data_cb does not return data chunks. It will be call arbitrarally * http_data_cb does not return data chunks. It will be call arbitrarally
* many times for each string. E.G. you might get 10 callbacks for "on_path" * many times for each string. E.G. you might get 10 callbacks for "on_url"
* each providing just a few characters more data. * each providing just a few characters more data.
*/ */
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
@@ -85,43 +76,44 @@ typedef int (*http_cb) (http_parser*);
/* Request Methods */ /* Request Methods */
#define HTTP_METHOD_MAP(XX) \ #define HTTP_METHOD_MAP(XX) \
XX(0, DELETE) \ XX(0, DELETE, DELETE) \
XX(1, GET) \ XX(1, GET, GET) \
XX(2, HEAD) \ XX(2, HEAD, HEAD) \
XX(3, POST) \ XX(3, POST, POST) \
XX(4, PUT) \ XX(4, PUT, PUT) \
/* pathological */ \ /* pathological */ \
XX(5, CONNECT) \ XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS) \ XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE) \ XX(7, TRACE, TRACE) \
/* webdav */ \ /* webdav */ \
XX(8, COPY) \ XX(8, COPY, COPY) \
XX(9, LOCK) \ XX(9, LOCK, LOCK) \
XX(10, MKCOL) \ XX(10, MKCOL, MKCOL) \
XX(11, MOVE) \ XX(11, MOVE, MOVE) \
XX(12, PROPFIND) \ XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH) \ XX(13, PROPPATCH, PROPPATCH) \
XX(14, UNLOCK) \ XX(14, SEARCH, SEARCH) \
/* subversion */ \ XX(15, UNLOCK, UNLOCK) \
XX(15, REPORT) \ /* subversion */ \
XX(16, MKACTIVITY) \ XX(16, REPORT, REPORT) \
XX(17, CHECKOUT) \ XX(17, MKACTIVITY, MKACTIVITY) \
XX(18, MERGE) \ XX(18, CHECKOUT, CHECKOUT) \
/* upnp */ \ XX(19, MERGE, MERGE) \
XX(19, MSEARCH) \ /* upnp */ \
XX(20, NOTIFY) \ XX(20, MSEARCH, M-SEARCH) \
XX(21, SUBSCRIBE) \ XX(21, NOTIFY, NOTIFY) \
XX(22, UNSUBSCRIBE) \ XX(22, SUBSCRIBE, SUBSCRIBE) \
/* RFC-5789 */ \ XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \
XX(23, PATCH) \ /* RFC-5789 */ \
XX(24, PURGE) \ XX(24, PATCH, PATCH) \
XX(25, PURGE, PURGE) \
enum http_method enum http_method
{ {
#define XX(num, name) HTTP_##name = num, #define XX(num, name, string) HTTP_##name = num,
HTTP_METHOD_MAP(XX) HTTP_METHOD_MAP(XX)
#undef X #undef XX
}; };
@@ -149,6 +141,7 @@ enum flags
\ \
/* Callback-related errors */ \ /* Callback-related errors */ \
XX(CB_message_begin, "the on_message_begin callback failed") \ XX(CB_message_begin, "the on_message_begin callback failed") \
XX(CB_status_complete, "the on_status_complete callback failed") \
XX(CB_url, "the on_url callback failed") \ XX(CB_url, "the on_url callback failed") \
XX(CB_header_field, "the on_header_field callback failed") \ XX(CB_header_field, "the on_header_field callback failed") \
XX(CB_header_value, "the on_header_value callback failed") \ XX(CB_header_value, "the on_header_value callback failed") \
@@ -195,13 +188,6 @@ enum http_errno {
/* Get an http_errno value from an http_parser */ /* Get an http_errno value from an http_parser */
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
/* Get the line number that generated the current error */
#if HTTP_PARSER_DEBUG
#define HTTP_PARSER_ERRNO_LINE(p) ((p)->error_lineno)
#else
#define HTTP_PARSER_ERRNO_LINE(p) 0
#endif
struct http_parser { struct http_parser {
/** PRIVATE **/ /** PRIVATE **/
@@ -228,10 +214,6 @@ struct http_parser {
*/ */
unsigned char upgrade : 1; unsigned char upgrade : 1;
#if HTTP_PARSER_DEBUG
uint32_t error_lineno;
#endif
/** PUBLIC **/ /** PUBLIC **/
void *data; /* A pointer to get hook to the "connection" or "socket" object */ void *data; /* A pointer to get hook to the "connection" or "socket" object */
}; };
@@ -240,6 +222,7 @@ struct http_parser {
struct http_parser_settings { struct http_parser_settings {
http_cb on_message_begin; http_cb on_message_begin;
http_data_cb on_url; http_data_cb on_url;
http_cb on_status_complete;
http_data_cb on_header_field; http_data_cb on_header_field;
http_data_cb on_header_value; http_data_cb on_header_value;
http_cb on_headers_complete; http_cb on_headers_complete;
@@ -255,7 +238,8 @@ enum http_parser_url_fields
, UF_PATH = 3 , UF_PATH = 3
, UF_QUERY = 4 , UF_QUERY = 4
, UF_FRAGMENT = 5 , UF_FRAGMENT = 5
, UF_MAX = 6 , UF_USERINFO = 6
, UF_MAX = 7
}; };
@@ -287,12 +271,12 @@ size_t http_parser_execute(http_parser *parser,
/* If http_should_keep_alive() in the on_headers_complete or /* If http_should_keep_alive() in the on_headers_complete or
* on_message_complete callback returns true, then this will be should be * on_message_complete callback returns 0, then this should be
* the last message on the connection. * the last message on the connection.
* If you are the server, respond with the "Connection: close" header. * If you are the server, respond with the "Connection: close" header.
* If you are the client, close the connection. * If you are the client, close the connection.
*/ */
int http_should_keep_alive(http_parser *parser); int http_should_keep_alive(const http_parser *parser);
/* Returns a string version of the HTTP method. */ /* Returns a string version of the HTTP method. */
const char *http_method_str(enum http_method m); const char *http_method_str(enum http_method m);
@@ -311,6 +295,9 @@ int http_parser_parse_url(const char *buf, size_t buflen,
/* Pause or un-pause the parser; a nonzero value pauses */ /* Pause or un-pause the parser; a nonzero value pauses */
void http_parser_pause(http_parser *parser, int paused); void http_parser_pause(http_parser *parser, int paused);
/* Checks if this is the final chunk of the body. */
int http_body_is_final(const http_parser *parser);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
+10 -2
View File
@@ -62,10 +62,10 @@ static int scan_units(char *s, uint64_t *n, units *m) {
if ((c = sscanf(s, "%"SCNu64"%2s", &base, unit)) < 1) return -1; if ((c = sscanf(s, "%"SCNu64"%2s", &base, unit)) < 1) return -1;
if (c == 2) { if (c == 2 && strncasecmp(unit, m->base, 3)) {
for (i = 0; m->units[i] != NULL; i++) { for (i = 0; m->units[i] != NULL; i++) {
scale *= m->scale; scale *= m->scale;
if (!strncasecmp(unit, m->units[i], sizeof(unit))) break; if (!strncasecmp(unit, m->units[i], 3)) break;
} }
if (m->units[i] == NULL) return -1; if (m->units[i] == NULL) return -1;
} }
@@ -91,6 +91,14 @@ char *format_time_us(long double n) {
return format_units(n, units, 2); return format_units(n, units, 2);
} }
char *format_time_s(long double n) {
return format_units(n, &time_units_s, 0);
}
int scan_metric(char *s, uint64_t *n) { int scan_metric(char *s, uint64_t *n) {
return scan_units(s, n, &metric_units); return scan_units(s, n, &metric_units);
} }
int scan_time(char *s, uint64_t *n) {
return scan_units(s, n, &time_units_s);
}
+2
View File
@@ -4,7 +4,9 @@
char *format_binary(long double); char *format_binary(long double);
char *format_metric(long double); char *format_metric(long double);
char *format_time_us(long double); char *format_time_us(long double);
char *format_time_s(long double);
int scan_metric(char *, uint64_t *); int scan_metric(char *, uint64_t *);
int scan_time(char *, uint64_t *);
#endif /* UNITS_H */ #endif /* UNITS_H */
+67 -34
View File
@@ -31,7 +31,7 @@ static struct config {
struct addrinfo addr; struct addrinfo addr;
uint64_t threads; uint64_t threads;
uint64_t connections; uint64_t connections;
uint64_t requests; uint64_t duration;
uint64_t timeout; uint64_t timeout;
} cfg; } cfg;
@@ -50,17 +50,25 @@ static const struct http_parser_settings parser_settings = {
.on_message_complete = request_complete .on_message_complete = request_complete
}; };
static volatile sig_atomic_t stop = 0;
static void handler(int sig) {
stop = 1;
}
static void usage() { static void usage() {
printf("Usage: wrk <options> <url> \n" printf("Usage: wrk <options> <url> \n"
" Options: \n" " Options: \n"
" -c, --connections <n> Connections to keep open \n" " -c, --connections <N> Connections to keep open \n"
" -r, --requests <n> Total requests to make \n" " -d, --duration <T> Duration of test \n"
" -t, --threads <n> Number of threads to use \n" " -t, --threads <N> Number of threads to use \n"
" \n" " \n"
" -H, --header <h> Add header to request \n" " -H, --header <H> Add header to request \n"
" --timeout <T> Socket/request timeout \n"
" -v, --version Print version details \n" " -v, --version Print version details \n"
" \n" " \n"
" Numeric arguments may include a SI unit (2k, 2M, 2G)\n"); " Numeric arguments may include a SI unit (1k, 1M, 1G)\n"
" Time arguments may include a time unit (2s, 2m, 2h)\n");
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
@@ -104,14 +112,9 @@ int main(int argc, char **argv) {
for (addr = addrs; addr != NULL; addr = addr->ai_next) { for (addr = addrs; addr != NULL; addr = addr->ai_next) {
int fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); int fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (fd == -1) continue; if (fd == -1) continue;
if (connect(fd, addr->ai_addr, addr->ai_addrlen) == -1) { rc = connect(fd, addr->ai_addr, addr->ai_addrlen);
if (errno == EHOSTUNREACH || errno == ECONNREFUSED) {
close(fd);
continue;
}
}
close(fd); close(fd);
break; if (rc == 0) break;
} }
if (addr == NULL) { if (addr == NULL) {
@@ -121,6 +124,7 @@ int main(int argc, char **argv) {
} }
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
signal(SIGINT, SIG_IGN);
cfg.addr = *addr; cfg.addr = *addr;
request.buf = format_request(host, port, path, headers); request.buf = format_request(host, port, path, headers);
request.size = strlen(request.buf); request.size = strlen(request.buf);
@@ -131,12 +135,12 @@ int main(int argc, char **argv) {
thread *threads = zcalloc(cfg.threads * sizeof(thread)); thread *threads = zcalloc(cfg.threads * sizeof(thread));
uint64_t connections = cfg.connections / cfg.threads; uint64_t connections = cfg.connections / cfg.threads;
uint64_t requests = cfg.requests / cfg.threads; uint64_t stop_at = time_us() + (cfg.duration * 1000000);
for (uint64_t i = 0; i < cfg.threads; i++) { for (uint64_t i = 0; i < cfg.threads; i++) {
thread *t = &threads[i]; thread *t = &threads[i];
t->connections = connections; t->connections = connections;
t->requests = requests; t->stop_at = stop_at;
if (pthread_create(&t->thread, NULL, &thread_main, t)) { if (pthread_create(&t->thread, NULL, &thread_main, t)) {
char *msg = strerror(errno); char *msg = strerror(errno);
@@ -145,7 +149,15 @@ int main(int argc, char **argv) {
} }
} }
printf("Making %"PRIu64" requests to %s\n", cfg.requests, url); struct sigaction sa = {
.sa_handler = handler,
.sa_flags = 0,
};
sigfillset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
char *time = format_time_s(cfg.duration);
printf("Running %s test @ %s\n", time, url);
printf(" %"PRIu64" threads and %"PRIu64" connections\n", cfg.threads, cfg.connections); printf(" %"PRIu64" threads and %"PRIu64" connections\n", cfg.threads, cfg.connections);
uint64_t start = time_us(); uint64_t start = time_us();
@@ -282,17 +294,20 @@ static int sample_rate(aeEventLoop *loop, long long id, void *data) {
static int request_complete(http_parser *parser) { static int request_complete(http_parser *parser) {
connection *c = parser->data; connection *c = parser->data;
thread *thread = c->thread; thread *thread = c->thread;
uint64_t now = time_us();
thread->complete++;
c->latency = now - c->start;
if (parser->status_code > 399) { if (parser->status_code > 399) {
thread->errors.status++; thread->errors.status++;
} }
if (++thread->complete >= thread->requests) { if (now >= thread->stop_at) {
aeStop(thread->loop); aeStop(thread->loop);
goto done; goto done;
} }
c->latency = time_us() - c->start;
if (!http_should_keep_alive(parser)) goto reconnect; if (!http_should_keep_alive(parser)) goto reconnect;
http_parser_init(parser, HTTP_RESPONSE); http_parser_init(parser, HTTP_RESPONSE);
@@ -310,9 +325,10 @@ static int request_complete(http_parser *parser) {
static int check_timeouts(aeEventLoop *loop, long long id, void *data) { static int check_timeouts(aeEventLoop *loop, long long id, void *data) {
thread *thread = data; thread *thread = data;
connection *c = thread->cs; connection *c = thread->cs;
uint64_t now = time_us();
uint64_t maxAge = time_us() - (cfg.timeout * 1000); uint64_t maxAge = now - (cfg.timeout * 1000);
for (uint64_t i = 0; i < thread->connections; i++, c++) { for (uint64_t i = 0; i < thread->connections; i++, c++) {
if (maxAge > c->start) { if (maxAge > c->start) {
@@ -320,6 +336,10 @@ static int check_timeouts(aeEventLoop *loop, long long id, void *data) {
} }
} }
if (stop || now >= thread->stop_at) {
aeStop(loop);
}
return TIMEOUT_INTERVAL_MS; return TIMEOUT_INTERVAL_MS;
} }
@@ -382,26 +402,33 @@ static char *extract_url_part(char *url, struct http_parser_url *parser_url, enu
} }
static char *format_request(char *host, char *port, char *path, char **headers) { static char *format_request(char *host, char *port, char *path, char **headers) {
char *req = NULL; char *req = NULL;
char *head = NULL;
aprintf(&req, "GET %s HTTP/1.1\r\n", path);
aprintf(&req, "Host: %s", host);
if (port) aprintf(&req, ":%s", port);
aprintf(&req, "\r\n");
for (char **h = headers; *h != NULL; h++) { for (char **h = headers; *h != NULL; h++) {
aprintf(&req, "%s\r\n", *h); aprintf(&head, "%s\r\n", *h);
if (!strncasecmp(*h, "Host:", 5)) {
host = NULL;
port = NULL;
}
} }
aprintf(&req, "\r\n"); aprintf(&req, "GET %s HTTP/1.1\r\n", path);
if (host) aprintf(&req, "Host: %s", host);
if (port) aprintf(&req, ":%s", port);
if (host) aprintf(&req, "\r\n");
aprintf(&req, "%s\r\n", head ? head : "");
free(head);
return req; return req;
} }
static struct option longopts[] = { static struct option longopts[] = {
{ "connections", required_argument, NULL, 'c' }, { "connections", required_argument, NULL, 'c' },
{ "requests", required_argument, NULL, 'r' }, { "duration", required_argument, NULL, 'd' },
{ "threads", required_argument, NULL, 't' }, { "threads", required_argument, NULL, 't' },
{ "header", required_argument, NULL, 'H' }, { "header", required_argument, NULL, 'H' },
{ "timeout", required_argument, NULL, 'T' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'v' }, { "version", no_argument, NULL, 'v' },
{ NULL, 0, NULL, 0 } { NULL, 0, NULL, 0 }
@@ -413,10 +440,10 @@ static int parse_args(struct config *cfg, char **url, char **headers, int argc,
memset(cfg, 0, sizeof(struct config)); memset(cfg, 0, sizeof(struct config));
cfg->threads = 2; cfg->threads = 2;
cfg->connections = 10; cfg->connections = 10;
cfg->requests = 100; cfg->duration = 10;
cfg->timeout = SOCKET_TIMEOUT_MS; cfg->timeout = SOCKET_TIMEOUT_MS;
while ((c = getopt_long(argc, argv, "t:c:r:H:v?", longopts, NULL)) != -1) { while ((c = getopt_long(argc, argv, "t:c:d:H:T:rv?", longopts, NULL)) != -1) {
switch (c) { switch (c) {
case 't': case 't':
if (scan_metric(optarg, &cfg->threads)) return -1; if (scan_metric(optarg, &cfg->threads)) return -1;
@@ -424,16 +451,22 @@ static int parse_args(struct config *cfg, char **url, char **headers, int argc,
case 'c': case 'c':
if (scan_metric(optarg, &cfg->connections)) return -1; if (scan_metric(optarg, &cfg->connections)) return -1;
break; break;
case 'r': case 'd':
if (scan_metric(optarg, &cfg->requests)) return -1; if (scan_time(optarg, &cfg->duration)) return -1;
break; break;
case 'H': case 'H':
*header++ = optarg; *header++ = optarg;
break; break;
case 'T':
if (scan_time(optarg, &cfg->timeout)) return -1;
cfg->timeout *= 1000;
break;
case 'v': case 'v':
printf("wrk %s [%s] ", VERSION, aeGetApiName()); printf("wrk %s [%s] ", VERSION, aeGetApiName());
printf("Copyright (C) 2012 Will Glozer\n"); printf("Copyright (C) 2012 Will Glozer\n");
break; break;
case 'r':
fprintf(stderr, "wrk 2.0.0+ uses -d instead of -r\n");
case 'h': case 'h':
case '?': case '?':
case ':': case ':':
@@ -442,7 +475,7 @@ static int parse_args(struct config *cfg, char **url, char **headers, int argc,
} }
} }
if (optind == argc || !cfg->threads || !cfg->requests) return -1; if (optind == argc || !cfg->threads || !cfg->duration) return -1;
if (!cfg->connections || cfg->connections < cfg->threads) { if (!cfg->connections || cfg->connections < cfg->threads) {
fprintf(stderr, "number of connections must be >= threads\n"); fprintf(stderr, "number of connections must be >= threads\n");
+2 -2
View File
@@ -11,7 +11,7 @@
#include "http_parser.h" #include "http_parser.h"
#include "tinymt64.h" #include "tinymt64.h"
#define VERSION "1.1.0" #define VERSION "2.0.0"
#define RECVBUF 8192 #define RECVBUF 8192
#define SAMPLES 100000 #define SAMPLES 100000
@@ -31,7 +31,7 @@ typedef struct {
pthread_t thread; pthread_t thread;
aeEventLoop *loop; aeEventLoop *loop;
uint64_t connections; uint64_t connections;
uint64_t requests; uint64_t stop_at;
uint64_t complete; uint64_t complete;
uint64_t bytes; uint64_t bytes;
uint64_t start; uint64_t start;