comparison third_party/wrk/src/script.c @ 178:94705b5986b3

[ThirdParty] Added WRK and luajit for load testing.
author MrJuneJune <me@mrjunejune.com>
date Thu, 22 Jan 2026 20:10:30 -0800
parents
children
comparison
equal deleted inserted replaced
177:24fe8ff94056 178:94705b5986b3
1 // Copyright (C) 2013 - Will Glozer. All rights reserved.
2
3 #include <stdlib.h>
4 #include <string.h>
5 #include "script.h"
6 #include "http_parser.h"
7 #include "zmalloc.h"
8
9 typedef struct {
10 char *name;
11 int type;
12 void *value;
13 } table_field;
14
15 static int script_addr_tostring(lua_State *);
16 static int script_addr_gc(lua_State *);
17 static int script_stats_call(lua_State *);
18 static int script_stats_len(lua_State *);
19 static int script_stats_index(lua_State *);
20 static int script_thread_index(lua_State *);
21 static int script_thread_newindex(lua_State *);
22 static int script_wrk_lookup(lua_State *);
23 static int script_wrk_connect(lua_State *);
24
25 static void set_fields(lua_State *, int, const table_field *);
26 static void set_field(lua_State *, int, char *, int);
27 static int push_url_part(lua_State *, char *, struct http_parser_url *, enum http_parser_url_fields);
28
29 static const struct luaL_Reg addrlib[] = {
30 { "__tostring", script_addr_tostring },
31 { "__gc" , script_addr_gc },
32 { NULL, NULL }
33 };
34
35 static const struct luaL_Reg statslib[] = {
36 { "__call", script_stats_call },
37 { "__index", script_stats_index },
38 { "__len", script_stats_len },
39 { NULL, NULL }
40 };
41
42 static const struct luaL_Reg threadlib[] = {
43 { "__index", script_thread_index },
44 { "__newindex", script_thread_newindex },
45 { NULL, NULL }
46 };
47
48 lua_State *script_create(char *file, char *url, char **headers) {
49 lua_State *L = luaL_newstate();
50 luaL_openlibs(L);
51 (void) luaL_dostring(L, "wrk = require \"wrk\"");
52
53 luaL_newmetatable(L, "wrk.addr");
54 luaL_register(L, NULL, addrlib);
55 luaL_newmetatable(L, "wrk.stats");
56 luaL_register(L, NULL, statslib);
57 luaL_newmetatable(L, "wrk.thread");
58 luaL_register(L, NULL, threadlib);
59
60 struct http_parser_url parts = {};
61 script_parse_url(url, &parts);
62 char *path = "/";
63
64 if (parts.field_set & (1 << UF_PATH)) {
65 path = &url[parts.field_data[UF_PATH].off];
66 }
67
68 const table_field fields[] = {
69 { "lookup", LUA_TFUNCTION, script_wrk_lookup },
70 { "connect", LUA_TFUNCTION, script_wrk_connect },
71 { "path", LUA_TSTRING, path },
72 { NULL, 0, NULL },
73 };
74
75 lua_getglobal(L, "wrk");
76
77 set_field(L, 4, "scheme", push_url_part(L, url, &parts, UF_SCHEMA));
78 set_field(L, 4, "host", push_url_part(L, url, &parts, UF_HOST));
79 set_field(L, 4, "port", push_url_part(L, url, &parts, UF_PORT));
80 set_fields(L, 4, fields);
81
82 lua_getfield(L, 4, "headers");
83 for (char **h = headers; *h; h++) {
84 char *p = strchr(*h, ':');
85 if (p && p[1] == ' ') {
86 lua_pushlstring(L, *h, p - *h);
87 lua_pushstring(L, p + 2);
88 lua_settable(L, 5);
89 }
90 }
91 lua_pop(L, 5);
92
93 if (file && luaL_dofile(L, file)) {
94 const char *cause = lua_tostring(L, -1);
95 fprintf(stderr, "%s: %s\n", file, cause);
96 }
97
98 return L;
99 }
100
101 bool script_resolve(lua_State *L, char *host, char *service) {
102 lua_getglobal(L, "wrk");
103
104 lua_getfield(L, -1, "resolve");
105 lua_pushstring(L, host);
106 lua_pushstring(L, service);
107 lua_call(L, 2, 0);
108
109 lua_getfield(L, -1, "addrs");
110 size_t count = lua_objlen(L, -1);
111 lua_pop(L, 2);
112 return count > 0;
113 }
114
115 void script_push_thread(lua_State *L, thread *t) {
116 thread **ptr = (thread **) lua_newuserdata(L, sizeof(thread **));
117 *ptr = t;
118 luaL_getmetatable(L, "wrk.thread");
119 lua_setmetatable(L, -2);
120 }
121
122 void script_init(lua_State *L, thread *t, int argc, char **argv) {
123 lua_getglobal(t->L, "wrk");
124
125 script_push_thread(t->L, t);
126 lua_setfield(t->L, -2, "thread");
127
128 lua_getglobal(L, "wrk");
129 lua_getfield(L, -1, "setup");
130 script_push_thread(L, t);
131 lua_call(L, 1, 0);
132 lua_pop(L, 1);
133
134 lua_getfield(t->L, -1, "init");
135 lua_newtable(t->L);
136 for (int i = 0; i < argc; i++) {
137 lua_pushstring(t->L, argv[i]);
138 lua_rawseti(t->L, -2, i);
139 }
140 lua_call(t->L, 1, 0);
141 lua_pop(t->L, 1);
142 }
143
144 uint64_t script_delay(lua_State *L) {
145 lua_getglobal(L, "delay");
146 lua_call(L, 0, 1);
147 uint64_t delay = lua_tonumber(L, -1);
148 lua_pop(L, 1);
149 return delay;
150 }
151
152 void script_request(lua_State *L, char **buf, size_t *len) {
153 int pop = 1;
154 lua_getglobal(L, "request");
155 if (!lua_isfunction(L, -1)) {
156 lua_getglobal(L, "wrk");
157 lua_getfield(L, -1, "request");
158 pop += 2;
159 }
160 lua_call(L, 0, 1);
161 const char *str = lua_tolstring(L, -1, len);
162 *buf = realloc(*buf, *len);
163 memcpy(*buf, str, *len);
164 lua_pop(L, pop);
165 }
166
167 void script_response(lua_State *L, int status, buffer *headers, buffer *body) {
168 lua_getglobal(L, "response");
169 lua_pushinteger(L, status);
170 lua_newtable(L);
171
172 for (char *c = headers->buffer; c < headers->cursor; ) {
173 c = buffer_pushlstring(L, c);
174 c = buffer_pushlstring(L, c);
175 lua_rawset(L, -3);
176 }
177
178 lua_pushlstring(L, body->buffer, body->cursor - body->buffer);
179 lua_call(L, 3, 0);
180
181 buffer_reset(headers);
182 buffer_reset(body);
183 }
184
185 bool script_is_function(lua_State *L, char *name) {
186 lua_getglobal(L, name);
187 bool is_function = lua_isfunction(L, -1);
188 lua_pop(L, 1);
189 return is_function;
190 }
191
192 bool script_is_static(lua_State *L) {
193 return !script_is_function(L, "request");
194 }
195
196 bool script_want_response(lua_State *L) {
197 return script_is_function(L, "response");
198 }
199
200 bool script_has_delay(lua_State *L) {
201 return script_is_function(L, "delay");
202 }
203
204 bool script_has_done(lua_State *L) {
205 return script_is_function(L, "done");
206 }
207
208 void script_header_done(lua_State *L, luaL_Buffer *buffer) {
209 luaL_pushresult(buffer);
210 }
211
212 void script_summary(lua_State *L, uint64_t duration, uint64_t requests, uint64_t bytes) {
213 const table_field fields[] = {
214 { "duration", LUA_TNUMBER, &duration },
215 { "requests", LUA_TNUMBER, &requests },
216 { "bytes", LUA_TNUMBER, &bytes },
217 { NULL, 0, NULL },
218 };
219 lua_newtable(L);
220 set_fields(L, 1, fields);
221 }
222
223 void script_errors(lua_State *L, errors *errors) {
224 uint64_t e[] = {
225 errors->connect,
226 errors->read,
227 errors->write,
228 errors->status,
229 errors->timeout
230 };
231 const table_field fields[] = {
232 { "connect", LUA_TNUMBER, &e[0] },
233 { "read", LUA_TNUMBER, &e[1] },
234 { "write", LUA_TNUMBER, &e[2] },
235 { "status", LUA_TNUMBER, &e[3] },
236 { "timeout", LUA_TNUMBER, &e[4] },
237 { NULL, 0, NULL },
238 };
239 lua_newtable(L);
240 set_fields(L, 2, fields);
241 lua_setfield(L, 1, "errors");
242 }
243
244 void script_push_stats(lua_State *L, stats *s) {
245 stats **ptr = (stats **) lua_newuserdata(L, sizeof(stats **));
246 *ptr = s;
247 luaL_getmetatable(L, "wrk.stats");
248 lua_setmetatable(L, -2);
249 }
250
251 void script_done(lua_State *L, stats *latency, stats *requests) {
252 lua_getglobal(L, "done");
253 lua_pushvalue(L, 1);
254
255 script_push_stats(L, latency);
256 script_push_stats(L, requests);
257
258 lua_call(L, 3, 0);
259 lua_pop(L, 1);
260 }
261
262 static int verify_request(http_parser *parser) {
263 size_t *count = parser->data;
264 (*count)++;
265 return 0;
266 }
267
268 size_t script_verify_request(lua_State *L) {
269 http_parser_settings settings = {
270 .on_message_complete = verify_request
271 };
272 http_parser parser;
273 char *request = NULL;
274 size_t len, count = 0;
275
276 script_request(L, &request, &len);
277 http_parser_init(&parser, HTTP_REQUEST);
278 parser.data = &count;
279
280 size_t parsed = http_parser_execute(&parser, &settings, request, len);
281
282 if (parsed != len || count == 0) {
283 enum http_errno err = HTTP_PARSER_ERRNO(&parser);
284 const char *desc = http_errno_description(err);
285 const char *msg = err != HPE_OK ? desc : "incomplete request";
286 int line = 1, column = 1;
287
288 for (char *c = request; c < request + parsed; c++) {
289 column++;
290 if (*c == '\n') {
291 column = 1;
292 line++;
293 }
294 }
295
296 fprintf(stderr, "%s at %d:%d\n", msg, line, column);
297 exit(1);
298 }
299
300 return count;
301 }
302
303 static struct addrinfo *checkaddr(lua_State *L) {
304 struct addrinfo *addr = luaL_checkudata(L, -1, "wrk.addr");
305 luaL_argcheck(L, addr != NULL, 1, "`addr' expected");
306 return addr;
307 }
308
309 void script_addr_copy(struct addrinfo *src, struct addrinfo *dst) {
310 *dst = *src;
311 dst->ai_addr = zmalloc(src->ai_addrlen);
312 memcpy(dst->ai_addr, src->ai_addr, src->ai_addrlen);
313 }
314
315 struct addrinfo *script_addr_clone(lua_State *L, struct addrinfo *addr) {
316 struct addrinfo *udata = lua_newuserdata(L, sizeof(*udata));
317 luaL_getmetatable(L, "wrk.addr");
318 lua_setmetatable(L, -2);
319 script_addr_copy(addr, udata);
320 return udata;
321 }
322
323 static int script_addr_tostring(lua_State *L) {
324 struct addrinfo *addr = checkaddr(L);
325 char host[NI_MAXHOST];
326 char service[NI_MAXSERV];
327
328 int flags = NI_NUMERICHOST | NI_NUMERICSERV;
329 int rc = getnameinfo(addr->ai_addr, addr->ai_addrlen, host, NI_MAXHOST, service, NI_MAXSERV, flags);
330 if (rc != 0) {
331 const char *msg = gai_strerror(rc);
332 return luaL_error(L, "addr tostring failed %s", msg);
333 }
334
335 lua_pushfstring(L, "%s:%s", host, service);
336 return 1;
337 }
338
339 static int script_addr_gc(lua_State *L) {
340 struct addrinfo *addr = checkaddr(L);
341 zfree(addr->ai_addr);
342 return 0;
343 }
344
345 static stats *checkstats(lua_State *L) {
346 stats **s = luaL_checkudata(L, 1, "wrk.stats");
347 luaL_argcheck(L, s != NULL, 1, "`stats' expected");
348 return *s;
349 }
350
351 static int script_stats_percentile(lua_State *L) {
352 stats *s = checkstats(L);
353 lua_Number p = luaL_checknumber(L, 2);
354 lua_pushnumber(L, stats_percentile(s, p));
355 return 1;
356 }
357
358 static int script_stats_call(lua_State *L) {
359 stats *s = checkstats(L);
360 uint64_t index = lua_tonumber(L, 2);
361 uint64_t count;
362 lua_pushnumber(L, stats_value_at(s, index - 1, &count));
363 lua_pushnumber(L, count);
364 return 2;
365 }
366
367 static int script_stats_index(lua_State *L) {
368 stats *s = checkstats(L);
369 const char *method = lua_tostring(L, 2);
370 if (!strcmp("min", method)) lua_pushnumber(L, s->min);
371 if (!strcmp("max", method)) lua_pushnumber(L, s->max);
372 if (!strcmp("mean", method)) lua_pushnumber(L, stats_mean(s));
373 if (!strcmp("stdev", method)) lua_pushnumber(L, stats_stdev(s, stats_mean(s)));
374 if (!strcmp("percentile", method)) {
375 lua_pushcfunction(L, script_stats_percentile);
376 }
377 return 1;
378 }
379
380 static int script_stats_len(lua_State *L) {
381 stats *s = checkstats(L);
382 lua_pushinteger(L, stats_popcount(s));
383 return 1;
384 }
385
386 static thread *checkthread(lua_State *L) {
387 thread **t = luaL_checkudata(L, 1, "wrk.thread");
388 luaL_argcheck(L, t != NULL, 1, "`thread' expected");
389 return *t;
390 }
391
392 static int script_thread_get(lua_State *L) {
393 thread *t = checkthread(L);
394 const char *key = lua_tostring(L, -1);
395 lua_getglobal(t->L, key);
396 script_copy_value(t->L, L, -1);
397 lua_pop(t->L, 1);
398 return 1;
399 }
400
401 static int script_thread_set(lua_State *L) {
402 thread *t = checkthread(L);
403 const char *name = lua_tostring(L, -2);
404 script_copy_value(L, t->L, -1);
405 lua_setglobal(t->L, name);
406 return 0;
407 }
408
409 static int script_thread_stop(lua_State *L) {
410 thread *t = checkthread(L);
411 aeStop(t->loop);
412 return 0;
413 }
414
415 static int script_thread_index(lua_State *L) {
416 thread *t = checkthread(L);
417 const char *key = lua_tostring(L, 2);
418 if (!strcmp("get", key)) lua_pushcfunction(L, script_thread_get);
419 if (!strcmp("set", key)) lua_pushcfunction(L, script_thread_set);
420 if (!strcmp("stop", key)) lua_pushcfunction(L, script_thread_stop);
421 if (!strcmp("addr", key)) script_addr_clone(L, t->addr);
422 return 1;
423 }
424
425 static int script_thread_newindex(lua_State *L) {
426 thread *t = checkthread(L);
427 const char *key = lua_tostring(L, -2);
428 if (!strcmp("addr", key)) {
429 struct addrinfo *addr = checkaddr(L);
430 if (t->addr) zfree(t->addr->ai_addr);
431 t->addr = zrealloc(t->addr, sizeof(*addr));
432 script_addr_copy(addr, t->addr);
433 } else {
434 luaL_error(L, "cannot set '%s' on thread", luaL_typename(L, -1));
435 }
436 return 0;
437 }
438
439 static int script_wrk_lookup(lua_State *L) {
440 struct addrinfo *addrs;
441 struct addrinfo hints = {
442 .ai_family = AF_UNSPEC,
443 .ai_socktype = SOCK_STREAM
444 };
445 int rc, index = 1;
446
447 const char *host = lua_tostring(L, -2);
448 const char *service = lua_tostring(L, -1);
449
450 if ((rc = getaddrinfo(host, service, &hints, &addrs)) != 0) {
451 const char *msg = gai_strerror(rc);
452 fprintf(stderr, "unable to resolve %s:%s %s\n", host, service, msg);
453 exit(1);
454 }
455
456 lua_newtable(L);
457 for (struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next) {
458 script_addr_clone(L, addr);
459 lua_rawseti(L, -2, index++);
460 }
461
462 freeaddrinfo(addrs);
463 return 1;
464 }
465
466 static int script_wrk_connect(lua_State *L) {
467 struct addrinfo *addr = checkaddr(L);
468 int fd, connected = 0;
469 if ((fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) != -1) {
470 connected = connect(fd, addr->ai_addr, addr->ai_addrlen) == 0;
471 close(fd);
472 }
473 lua_pushboolean(L, connected);
474 return 1;
475 }
476
477 void script_copy_value(lua_State *src, lua_State *dst, int index) {
478 switch (lua_type(src, index)) {
479 case LUA_TBOOLEAN:
480 lua_pushboolean(dst, lua_toboolean(src, index));
481 break;
482 case LUA_TNIL:
483 lua_pushnil(dst);
484 break;
485 case LUA_TNUMBER:
486 lua_pushnumber(dst, lua_tonumber(src, index));
487 break;
488 case LUA_TSTRING:
489 lua_pushstring(dst, lua_tostring(src, index));
490 break;
491 case LUA_TTABLE:
492 lua_newtable(dst);
493 lua_pushnil(src);
494 while (lua_next(src, index - 1)) {
495 script_copy_value(src, dst, -2);
496 script_copy_value(src, dst, -1);
497 lua_settable(dst, -3);
498 lua_pop(src, 1);
499 }
500 lua_pop(src, 1);
501 break;
502 default:
503 luaL_error(src, "cannot transfer '%s' to thread", luaL_typename(src, index));
504 }
505 }
506
507 int script_parse_url(char *url, struct http_parser_url *parts) {
508 if (!http_parser_parse_url(url, strlen(url), 0, parts)) {
509 if (!(parts->field_set & (1 << UF_SCHEMA))) return 0;
510 if (!(parts->field_set & (1 << UF_HOST))) return 0;
511 return 1;
512 }
513 return 0;
514 }
515
516 static int push_url_part(lua_State *L, char *url, struct http_parser_url *parts, enum http_parser_url_fields field) {
517 int type = parts->field_set & (1 << field) ? LUA_TSTRING : LUA_TNIL;
518 uint16_t off, len;
519 switch (type) {
520 case LUA_TSTRING:
521 off = parts->field_data[field].off;
522 len = parts->field_data[field].len;
523 lua_pushlstring(L, &url[off], len);
524 break;
525 case LUA_TNIL:
526 lua_pushnil(L);
527 }
528 return type;
529 }
530
531 static void set_field(lua_State *L, int index, char *field, int type) {
532 (void) type;
533 lua_setfield(L, index, field);
534 }
535
536 static void set_fields(lua_State *L, int index, const table_field *fields) {
537 for (int i = 0; fields[i].name; i++) {
538 table_field f = fields[i];
539 switch (f.value == NULL ? LUA_TNIL : f.type) {
540 case LUA_TFUNCTION:
541 lua_pushcfunction(L, (lua_CFunction) f.value);
542 break;
543 case LUA_TNUMBER:
544 lua_pushinteger(L, *((lua_Integer *) f.value));
545 break;
546 case LUA_TSTRING:
547 lua_pushstring(L, (const char *) f.value);
548 break;
549 case LUA_TNIL:
550 lua_pushnil(L);
551 break;
552 }
553 lua_setfield(L, index, f.name);
554 }
555 }
556
557 void buffer_append(buffer *b, const char *data, size_t len) {
558 size_t used = b->cursor - b->buffer;
559 while (used + len + 1 >= b->length) {
560 b->length += 1024;
561 b->buffer = realloc(b->buffer, b->length);
562 b->cursor = b->buffer + used;
563 }
564 memcpy(b->cursor, data, len);
565 b->cursor += len;
566 }
567
568 void buffer_reset(buffer *b) {
569 b->cursor = b->buffer;
570 }
571
572 char *buffer_pushlstring(lua_State *L, char *start) {
573 char *end = strchr(start, 0);
574 lua_pushlstring(L, start, end - start);
575 return end + 1;
576 }