From 9cff098ef7c084dec452ba67c667a783038004ff Mon Sep 17 00:00:00 2001 From: fangxinmiao Date: Tue, 29 Mar 2016 01:03:02 +0800 Subject: [PATCH 01/12] Add an option '-r' to limit the number of requests. --- src/ae.c | 15 +++++++++++++++ src/ae.h | 5 +++++ src/wrk.c | 45 +++++++++++++++++++++++++++++++++++++++------ src/wrk.h | 1 + 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/ae.c b/src/ae.c index 90be4e28..a95ef2a0 100644 --- a/src/ae.c +++ b/src/ae.c @@ -75,6 +75,8 @@ aeEventLoop *aeCreateEventLoop(int setsize) { eventLoop->stop = 0; eventLoop->maxfd = -1; eventLoop->beforesleep = NULL; + eventLoop->checkThreadStop = NULL; + eventLoop->checkThreadStopData = NULL; if (aeApiCreate(eventLoop) == -1) goto err; /* Events with mask == AE_NONE are not set. So let's initialize the * vector with it. */ @@ -386,6 +388,13 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags) fe->wfileProc(eventLoop,fd,fe->clientData,mask); } processed++; + + if (eventLoop->checkThreadStop != NULL) { + if (eventLoop->checkThreadStop(eventLoop) == 1) { + eventLoop->stop = 1; + break; + } + } } } /* Check time events */ @@ -433,3 +442,9 @@ char *aeGetApiName(void) { void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep) { eventLoop->beforesleep = beforesleep; } + +void aeSetCheckThreadStopProc(aeEventLoop *eventLoop, + aeCheckThreadStopProc *checkThreadStop, void *checkData) { + eventLoop->checkThreadStop = checkThreadStop; + eventLoop->checkThreadStopData = checkData; +} diff --git a/src/ae.h b/src/ae.h index 4d895024..3e3d6e78 100644 --- a/src/ae.h +++ b/src/ae.h @@ -57,6 +57,7 @@ typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData); typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData); typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop); +typedef int aeCheckThreadStopProc(struct aeEventLoop *eventLoop); /* File event structure */ typedef struct aeFileEvent { @@ -95,6 +96,8 @@ typedef struct aeEventLoop { int stop; void *apidata; /* This is used for polling API specific data */ aeBeforeSleepProc *beforesleep; + aeCheckThreadStopProc *checkThreadStop; + void *checkThreadStopData; } aeEventLoop; /* Prototypes */ @@ -114,5 +117,7 @@ int aeWait(int fd, int mask, long long milliseconds); void aeMain(aeEventLoop *eventLoop); char *aeGetApiName(void); void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep); +void aeSetCheckThreadStopProc(aeEventLoop *eventLoop, + aeCheckThreadStopProc *checkThreadStop, void *checkData); #endif diff --git a/src/wrk.c b/src/wrk.c index 51f46f72..642bfbe1 100644 --- a/src/wrk.c +++ b/src/wrk.c @@ -8,6 +8,7 @@ static struct config { uint64_t connections; uint64_t duration; uint64_t threads; + uint64_t requests; uint64_t timeout; uint64_t pipeline; bool delay; @@ -41,12 +42,20 @@ static void handler(int sig) { stop = 1; } +static int aeCheckThreadStop(struct aeEventLoop *eventLoop) { + thread *t = (thread *)eventLoop->checkThreadStopData; + if (t->complete == t->complete_stop) + return 1; + return 0; +} + static void usage() { printf("Usage: wrk \n" " Options: \n" " -c, --connections Connections to keep open \n" " -d, --duration Duration of test \n" " -t, --threads Number of threads to use \n" + " -r, --requests Number of requests to limit\n" " \n" " -s, --script Load Lua script file \n" " -H, --header Add header to request \n" @@ -102,13 +111,24 @@ int main(int argc, char **argv) { cfg.host = host; for (uint64_t i = 0; i < cfg.threads; i++) { - thread *t = &threads[i]; - t->loop = aeCreateEventLoop(10 + cfg.connections * 3); + thread *t = &threads[i]; + t->loop = aeCreateEventLoop(10 + cfg.connections * 3); + if (t->loop == NULL) { + char *msg = strerror(errno); + fprintf(stderr, "unable to create ae eventloop: %s\n", msg); + exit(2); + } + t->connections = cfg.connections / cfg.threads; + t->complete_stop = cfg.requests / cfg.threads; + if (i == (cfg.threads - 1)) + t->complete_stop += (cfg.requests % cfg.threads); t->L = script_create(cfg.script, url, headers); script_init(L, t, argc - optind, &argv[optind]); + aeSetCheckThreadStopProc(t->loop, aeCheckThreadStop, (void *)t); + if (i == 0) { cfg.pipeline = script_verify_request(t->L); cfg.dynamic = !script_is_static(t->L); @@ -120,7 +140,7 @@ int main(int argc, char **argv) { } } - if (!t->loop || pthread_create(&t->thread, NULL, &thread_main, t)) { + if (pthread_create(&t->thread, NULL, &thread_main, t)) { char *msg = strerror(errno); fprintf(stderr, "unable to create thread %"PRIu64": %s\n", i, msg); exit(2); @@ -143,8 +163,10 @@ int main(int argc, char **argv) { uint64_t bytes = 0; errors errors = { 0 }; - sleep(cfg.duration); - stop = 1; + if (cfg.duration > 0) { + sleep(cfg.duration); + stop = 1; + } for (uint64_t i = 0; i < cfg.threads; i++) { thread *t = &threads[i]; @@ -470,6 +492,7 @@ static struct option longopts[] = { { "connections", required_argument, NULL, 'c' }, { "duration", required_argument, NULL, 'd' }, { "threads", required_argument, NULL, 't' }, + { "requests", required_argument, NULL, 'r' }, { "script", required_argument, NULL, 's' }, { "header", required_argument, NULL, 'H' }, { "latency", no_argument, NULL, 'L' }, @@ -486,10 +509,11 @@ static int parse_args(struct config *cfg, char **url, struct http_parser_url *pa memset(cfg, 0, sizeof(struct config)); cfg->threads = 2; cfg->connections = 10; + cfg->requests = 0; cfg->duration = 10; cfg->timeout = SOCKET_TIMEOUT_MS; - while ((c = getopt_long(argc, argv, "t:c:d:s:H:T:Lrv?", longopts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "t:c:r:d:s:H:T:Lrv?", longopts, NULL)) != -1) { switch (c) { case 't': if (scan_metric(optarg, &cfg->threads)) return -1; @@ -497,6 +521,9 @@ static int parse_args(struct config *cfg, char **url, struct http_parser_url *pa case 'c': if (scan_metric(optarg, &cfg->connections)) return -1; break; + case 'r': + if (scan_metric(optarg, &cfg->requests)) return -1; + break; case 'd': if (scan_time(optarg, &cfg->duration)) return -1; break; @@ -537,6 +564,12 @@ static int parse_args(struct config *cfg, char **url, struct http_parser_url *pa return -1; } + if (cfg->requests > 0) + cfg->duration = 0; + + if (cfg->requests > 0 && cfg->requests < cfg->threads) + cfg->threads = cfg->requests; + *url = argv[optind]; *header = NULL; diff --git a/src/wrk.h b/src/wrk.h index 3fe77f14..1e87415f 100644 --- a/src/wrk.h +++ b/src/wrk.h @@ -36,6 +36,7 @@ typedef struct { lua_State *L; errors errors; struct connection *cs; + uint64_t complete_stop; } thread; typedef struct { From 5ab0ca4740d0a7c5c8f105a4f37867456e510e72 Mon Sep 17 00:00:00 2001 From: fangxinmiao Date: Sun, 3 Apr 2016 03:35:16 +0800 Subject: [PATCH 02/12] Fixed the bug introduce the previous commit(not send any request if none '-r' option). --- src/wrk.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/wrk.c b/src/wrk.c index 642bfbe1..832ba0d2 100644 --- a/src/wrk.c +++ b/src/wrk.c @@ -120,14 +120,15 @@ int main(int argc, char **argv) { } t->connections = cfg.connections / cfg.threads; - t->complete_stop = cfg.requests / cfg.threads; - if (i == (cfg.threads - 1)) - t->complete_stop += (cfg.requests % cfg.threads); - t->L = script_create(cfg.script, url, headers); script_init(L, t, argc - optind, &argv[optind]); - aeSetCheckThreadStopProc(t->loop, aeCheckThreadStop, (void *)t); + if (cfg.requests > 0) { + t->complete_stop = cfg.requests / cfg.threads; + if (i == (cfg.threads - 1)) + t->complete_stop += (cfg.requests % cfg.threads); + aeSetCheckThreadStopProc(t->loop, aeCheckThreadStop, (void *)t); + } if (i == 0) { cfg.pipeline = script_verify_request(t->L); From e44b77d5369a6b25b201f14d3e52be9036b8ae87 Mon Sep 17 00:00:00 2001 From: gruebel Date: Sun, 28 Jan 2018 23:03:19 +0000 Subject: [PATCH 03/12] fix issue with using WITH_LUAJIT parameter during build fixes #319 --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d555f0a9..79127448 100644 --- a/Makefile +++ b/Makefile @@ -30,11 +30,13 @@ DEPS := CFLAGS += -I$(ODIR)/include LDFLAGS += -L$(ODIR)/lib +LUAJIT_VER ?= luajit-2.1 + ifneq ($(WITH_LUAJIT),) - CFLAGS += -I$(WITH_LUAJIT)/include + CFLAGS += -I$(WITH_LUAJIT)/include/$(LUAJIT_VER) LDFLAGS += -L$(WITH_LUAJIT)/lib else - CFLAGS += -I$(ODIR)/include/luajit-2.1 + CFLAGS += -I$(ODIR)/include/$(LUAJIT_VER) DEPS += $(ODIR)/lib/libluajit-5.1.a endif From 7de3642de05e32ba4a51299168003affe14440c3 Mon Sep 17 00:00:00 2001 From: Anthony Monthe Date: Thu, 24 Jan 2019 01:34:25 +0000 Subject: [PATCH 04/12] Added JSON output --- src/wrk.c | 103 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 23 deletions(-) diff --git a/src/wrk.c b/src/wrk.c index 51f46f72..6fea89d0 100644 --- a/src/wrk.c +++ b/src/wrk.c @@ -13,6 +13,7 @@ static struct config { bool delay; bool dynamic; bool latency; + bool json_format; char *host; char *script; SSL_CTX *ctx; @@ -42,7 +43,7 @@ static void handler(int sig) { } static void usage() { - printf("Usage: wrk \n" + printf("Usage: wrk \n"// " Options: \n" " -c, --connections Connections to keep open \n" " -d, --duration Duration of test \n" @@ -51,6 +52,7 @@ static void usage() { " -s, --script Load Lua script file \n" " -H, --header Add header to request \n" " --latency Print latency statistics \n" + " --json-format Print output as JSON \n" " --timeout Socket/request timeout \n" " -v, --version Print version details \n" " \n" @@ -135,8 +137,10 @@ int main(int argc, char **argv) { 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); + if (cfg.json_format == false) { + printf("Running %s test @ %s\n", time, url); + printf(" %"PRIu64" threads and %"PRIu64" connections\n", cfg.threads, cfg.connections); + } uint64_t start = time_us(); uint64_t complete = 0; @@ -170,30 +174,78 @@ int main(int argc, char **argv) { stats_correct(statistics.latency, interval); } - print_stats_header(); - print_stats("Latency", statistics.latency, format_time_us); - print_stats("Req/Sec", statistics.requests, format_metric); - if (cfg.latency) print_stats_latency(statistics.latency); + if (cfg.json_format == false) { + print_stats_header(); + print_stats("Latency", statistics.latency, format_time_us); + print_stats("Req/Sec", statistics.requests, format_metric); + if (cfg.latency) print_stats_latency(statistics.latency); - char *runtime_msg = format_time_us(runtime_us); + char *runtime_msg = format_time_us(runtime_us); - printf(" %"PRIu64" requests in %s, %sB read\n", complete, runtime_msg, format_binary(bytes)); - if (errors.connect || errors.read || errors.write || errors.timeout) { - printf(" Socket errors: connect %d, read %d, write %d, timeout %d\n", - errors.connect, errors.read, errors.write, errors.timeout); - } + printf(" %"PRIu64" requests in %s, %sB read\n", complete, runtime_msg, format_binary(bytes)); + if (errors.connect || errors.read || errors.write || errors.timeout) { + printf(" Socket errors: connect %d, read %d, write %d, timeout %d\n", + errors.connect, errors.read, errors.write, errors.timeout); + } - if (errors.status) { - printf(" Non-2xx or 3xx responses: %d\n", errors.status); - } + if (errors.status) { + printf(" Non-2xx or 3xx responses: %d\n", errors.status); + } - printf("Requests/sec: %9.2Lf\n", req_per_s); - printf("Transfer/sec: %10sB\n", format_binary(bytes_per_s)); + printf("Requests/sec: %9.2Lf\n", req_per_s); + printf("Transfer/sec: %10sB\n", format_binary(bytes_per_s)); - if (script_has_done(L)) { - script_summary(L, runtime_us, complete, bytes); - script_errors(L, &errors); - script_done(L, statistics.latency, statistics.requests); + if (script_has_done(L)) { + script_summary(L, runtime_us, complete, bytes); + script_errors(L, &errors); + script_done(L, statistics.latency, statistics.requests); + } + } else { + long double req_mean = stats_mean(statistics.requests); + long double req_stdev = stats_stdev(statistics.requests, req_mean); + long double req_within = stats_within_stdev(statistics.requests, req_mean, req_stdev, 1); + long double lat_mean = stats_mean(statistics.latency); + long double lat_stdev = stats_stdev(statistics.latency, lat_mean); + long double lat_within = stats_within_stdev(statistics.latency, lat_mean, lat_stdev, 1); + long double lat_perc_50 = stats_percentile(statistics.latency, 50.0) / 1000000.0; + long double lat_perc_75 = stats_percentile(statistics.latency, 75.0) / 1000000.0; + long double lat_perc_90 = stats_percentile(statistics.latency, 90.0) / 1000000.0; + long double lat_perc_95 = stats_percentile(statistics.latency, 95.0) / 1000000.0; + long double lat_perc_99 = stats_percentile(statistics.latency, 99.0) / 1000000.0; + printf("{\n"); + printf(" \"threads\": %"PRIu64",\n", cfg.threads); + printf(" \"connections\": %"PRIu64",\n", cfg.connections); + printf(" \"duration\": %"PRIu64",\n", cfg.duration); + if (cfg.script) { + printf(" \"script\": %s,\n", cfg.script); + } else { + printf(" \"script\": null,\n"); + } + printf(" \"runtime\": %"PRIu64",\n", runtime_us); + printf(" \"bytes_per_sec\": %Lf,\n", bytes_per_s); + printf(" \"requests_per_sec\": %Lf,\n", req_per_s); + printf(" \"requests_mean\": %Lf,\n", req_mean); + printf(" \"requests_stdev\": %Lf,\n", req_stdev); + printf(" \"requests_min\": %"PRIu64",\n", statistics.requests->min); + printf(" \"requests_max\": %"PRIu64",\n", statistics.requests->max); + printf(" \"requests_within_stdev\": %.2Lf,\n", req_within); + printf(" \"latency_mean\": %Lf,\n", lat_mean/1000000.0); + printf(" \"latency_stdev\": %Lf,\n", lat_stdev/1000000.0); + printf(" \"latency_min\": %f,\n", statistics.latency->min/1000000.0); + printf(" \"latency_max\": %f,\n", statistics.latency->max/1000000.0); + printf(" \"latency_within_stdev\": %.2Lf,\n", lat_within); + printf(" \"latency_percentile_50\": %.2Lf,\n", lat_perc_50); + printf(" \"latency_percentile_75\": %.2Lf,\n", lat_perc_75); + printf(" \"latency_percentile_90\": %.2Lf,\n", lat_perc_90); + printf(" \"latency_percentile_90\": %.2Lf,\n", lat_perc_90); + printf(" \"latency_percentile_95\": %.2Lf,\n", lat_perc_95); + printf(" \"latency_percentile_99\": %.2Lf,\n", lat_perc_99); + printf(" \"errors_connect\": %d,\n", errors.connect); + printf(" \"errors_read\": %d,\n", errors.read); + printf(" \"errors_write\": %d,\n", errors.write); + printf(" \"errors_timeout\": %d,\n", errors.timeout); + printf(" \"errors_status\": %d\n", errors.status); + printf("}"); } return 0; @@ -474,6 +526,7 @@ static struct option longopts[] = { { "header", required_argument, NULL, 'H' }, { "latency", no_argument, NULL, 'L' }, { "timeout", required_argument, NULL, 'T' }, + { "json-format", no_argument, NULL, 'j' }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } @@ -488,8 +541,9 @@ static int parse_args(struct config *cfg, char **url, struct http_parser_url *pa cfg->connections = 10; cfg->duration = 10; cfg->timeout = SOCKET_TIMEOUT_MS; + cfg->json_format = false; - while ((c = getopt_long(argc, argv, "t:c:d:s:H:T:Lrv?", longopts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "t:c:d:s:H:T:Lrv?j", longopts, NULL)) != -1) { switch (c) { case 't': if (scan_metric(optarg, &cfg->threads)) return -1; @@ -509,6 +563,9 @@ static int parse_args(struct config *cfg, char **url, struct http_parser_url *pa case 'L': cfg->latency = true; break; + case 'j': + cfg->json_format = true; + break; case 'T': if (scan_time(optarg, &cfg->timeout)) return -1; cfg->timeout *= 1000; From 1039c065ab695498c363633b65bac0da7b591475 Mon Sep 17 00:00:00 2001 From: Anthony Monthe Date: Thu, 24 Jan 2019 01:35:09 +0000 Subject: [PATCH 05/12] Added VIM file to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e25de364..b0a9b64a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ obj/ wrk +*.sw[pon] From 134d2ded9f9e11d612023141d18e474c5174d068 Mon Sep 17 00:00:00 2001 From: Anthony Monthe Date: Thu, 24 Jan 2019 01:39:25 +0000 Subject: [PATCH 06/12] Update README about JSON --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ac61e0d9..7333020b 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ --latency: print detailed latency statistics + -j --json-format: print output as JSON format + --timeout: record a timeout if a response is not received within this amount of time. From 797961d952e818cde350d5db3815ccbfb83ff05e Mon Sep 17 00:00:00 2001 From: Anthony Monthe Date: Thu, 24 Jan 2019 02:40:57 +0000 Subject: [PATCH 07/12] Added more infos and little fixes --- src/wrk.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/wrk.c b/src/wrk.c index 6fea89d0..519b9f29 100644 --- a/src/wrk.c +++ b/src/wrk.c @@ -43,7 +43,7 @@ static void handler(int sig) { } static void usage() { - printf("Usage: wrk \n"// + printf("Usage: wrk \n" " Options: \n" " -c, --connections Connections to keep open \n" " -d, --duration Duration of test \n" @@ -52,7 +52,7 @@ static void usage() { " -s, --script Load Lua script file \n" " -H, --header Add header to request \n" " --latency Print latency statistics \n" - " --json-format Print output as JSON \n" + " -j --json-format Print output as JSON \n" " --timeout Socket/request timeout \n" " -v, --version Print version details \n" " \n" @@ -137,7 +137,7 @@ int main(int argc, char **argv) { sigaction(SIGINT, &sa, NULL); char *time = format_time_s(cfg.duration); - if (cfg.json_format == false) { + if (!cfg.json_format) { printf("Running %s test @ %s\n", time, url); printf(" %"PRIu64" threads and %"PRIu64" connections\n", cfg.threads, cfg.connections); } @@ -174,7 +174,7 @@ int main(int argc, char **argv) { stats_correct(statistics.latency, interval); } - if (cfg.json_format == false) { + if (!cfg.json_format) { print_stats_header(); print_stats("Latency", statistics.latency, format_time_us); print_stats("Req/Sec", statistics.requests, format_metric); @@ -213,16 +213,20 @@ int main(int argc, char **argv) { long double lat_perc_95 = stats_percentile(statistics.latency, 95.0) / 1000000.0; long double lat_perc_99 = stats_percentile(statistics.latency, 99.0) / 1000000.0; printf("{\n"); + printf(" \"url\": \"%s\",\n", url); printf(" \"threads\": %"PRIu64",\n", cfg.threads); printf(" \"connections\": %"PRIu64",\n", cfg.connections); printf(" \"duration\": %"PRIu64",\n", cfg.duration); + printf(" \"timeout\": %"PRIu64",\n", cfg.timeout); if (cfg.script) { - printf(" \"script\": %s,\n", cfg.script); + printf(" \"script\": \"%s\",\n", cfg.script); } else { printf(" \"script\": null,\n"); } - printf(" \"runtime\": %"PRIu64",\n", runtime_us); + printf(" \"runtime_us\": %"PRIu64",\n", runtime_us); + printf(" \"bytes\": %"PRIu64",\n", bytes); printf(" \"bytes_per_sec\": %Lf,\n", bytes_per_s); + printf(" \"requests_count\": %"PRIu64",\n", statistics.requests->count); printf(" \"requests_per_sec\": %Lf,\n", req_per_s); printf(" \"requests_mean\": %Lf,\n", req_mean); printf(" \"requests_stdev\": %Lf,\n", req_stdev); @@ -237,7 +241,6 @@ int main(int argc, char **argv) { printf(" \"latency_percentile_50\": %.2Lf,\n", lat_perc_50); printf(" \"latency_percentile_75\": %.2Lf,\n", lat_perc_75); printf(" \"latency_percentile_90\": %.2Lf,\n", lat_perc_90); - printf(" \"latency_percentile_90\": %.2Lf,\n", lat_perc_90); printf(" \"latency_percentile_95\": %.2Lf,\n", lat_perc_95); printf(" \"latency_percentile_99\": %.2Lf,\n", lat_perc_99); printf(" \"errors_connect\": %d,\n", errors.connect); From 215bf1ba6ead65695d6b2e278fff3a8332782573 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Sun, 25 Aug 2019 17:20:14 -0700 Subject: [PATCH 08/12] script: Allow passing script through stdin with - Fixes #359 --- src/script.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script.c b/src/script.c index 68a5d081..e6d8422e 100644 --- a/src/script.c +++ b/src/script.c @@ -90,7 +90,7 @@ lua_State *script_create(char *file, char *url, char **headers) { } lua_pop(L, 5); - if (file && luaL_dofile(L, file)) { + if (file && luaL_dofile(L, strcmp("-", file) ? file : NULL)) { const char *cause = lua_tostring(L, -1); fprintf(stderr, "%s: %s\n", file, cause); } From 8e9ca61fe87ba23daa3e2cbfd18c4b144ab66547 Mon Sep 17 00:00:00 2001 From: Sudiksha Date: Sat, 23 Nov 2019 23:00:08 +0530 Subject: [PATCH 09/12] Add p95 into the latency statistics --- src/wrk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wrk.c b/src/wrk.c index 51f46f72..2062a4f1 100644 --- a/src/wrk.c +++ b/src/wrk.c @@ -573,7 +573,7 @@ static void print_stats(char *name, stats *stats, char *(*fmt)(long double)) { } static void print_stats_latency(stats *stats) { - long double percentiles[] = { 50.0, 75.0, 90.0, 99.0 }; + long double percentiles[] = { 50.0, 75.0, 90.0, 95.0, 99.0 }; printf(" Latency Distribution\n"); for (size_t i = 0; i < sizeof(percentiles) / sizeof(long double); i++) { long double p = percentiles[i]; From a9ab7635a0c31f1091e3f3caff089e5b8fc1190e Mon Sep 17 00:00:00 2001 From: andrejlevkovitch Date: Tue, 18 Feb 2020 17:15:30 +0300 Subject: [PATCH 10/12] fix problem with sharing string: now it is passible to share non-null-terminated string --- src/script.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/script.c b/src/script.c index 68a5d081..e17f36dc 100644 --- a/src/script.c +++ b/src/script.c @@ -485,8 +485,11 @@ void script_copy_value(lua_State *src, lua_State *dst, int index) { case LUA_TNUMBER: lua_pushnumber(dst, lua_tonumber(src, index)); break; - case LUA_TSTRING: - lua_pushstring(dst, lua_tostring(src, index)); + case LUA_TSTRING: { + size_t len = 0; + const char *str = lua_tolstring(src, index, &len); + lua_pushlstring(dst, str, len); + } break; case LUA_TTABLE: lua_newtable(dst); From 87d1a94009f3698f68ff923db419910f6b7fed6f Mon Sep 17 00:00:00 2001 From: zhaoxiaobiao Date: Sat, 26 Sep 2020 22:33:30 +0800 Subject: [PATCH 11/12] add luacjosn lib --- Makefile | 4 +- scripts/post_json.lua | 9 + src/lua_cjson.c | 1299 +++++++++++++++++++++++++++++++++++++++++ src/script.c | 16 + src/strbuf.c | 251 ++++++++ src/strbuf.h | 142 +++++ 6 files changed, 1719 insertions(+), 2 deletions(-) create mode 100644 scripts/post_json.lua create mode 100644 src/lua_cjson.c create mode 100644 src/strbuf.c create mode 100644 src/strbuf.h diff --git a/Makefile b/Makefile index 03254812..4dca8bda 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,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 + ae.c zmalloc.c http_parser.c lua_cjson.c strbuf.c BIN := wrk VER ?= $(shell git describe --tags --always --dirty) @@ -87,7 +87,7 @@ $(ODIR)/$(OPENSSL): deps/$(OPENSSL).tar.gz | $(ODIR) $(ODIR)/lib/libluajit-5.1.a: $(ODIR)/$(LUAJIT) @echo Building LuaJIT... @$(MAKE) -C $< PREFIX=$(abspath $(ODIR)) BUILDMODE=static install - @cd $(ODIR)/bin && ln -s luajit-2.1.0-beta3 luajit + -@cd $(ODIR)/bin && ln -s luajit-2.1.0-beta3 luajit $(ODIR)/lib/libssl.a: $(ODIR)/$(OPENSSL) @echo Building OpenSSL... diff --git a/scripts/post_json.lua b/scripts/post_json.lua new file mode 100644 index 00000000..2c48f486 --- /dev/null +++ b/scripts/post_json.lua @@ -0,0 +1,9 @@ +-- example HTTP POST script which demonstrates setting the +-- HTTP method, body, and adding a header + +local cjson = require "cjson" +local json_encode = cjson.encode +local json_decode = cjson.decode +wrk.method = "POST" +wrk.body = json_encode({["key"] = "value"}) +wrk.headers["Content-Type"] = "application/x-www-form-urlencoded" diff --git a/src/lua_cjson.c b/src/lua_cjson.c new file mode 100644 index 00000000..2e272b00 --- /dev/null +++ b/src/lua_cjson.c @@ -0,0 +1,1299 @@ +#define VERSION "1.0.3" + +/* CJSON - JSON support for Lua + * + * Copyright (c) 2010-2011 Mark Pulford + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE 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. + */ + +/* Caveats: + * - JSON "null" values are represented as lightuserdata since Lua + * tables cannot contain "nil". Compare with cjson.null. + * - Invalid UTF-8 characters are not detected and will be passed + * untouched. If required, UTF-8 error checking should be done + * outside this library. + * - Javascript comments are not part of the JSON spec, and are not + * currently supported. + * + * Note: Decoding is slower than encoding. Lua spends significant + * time (30%) managing tables when parsing JSON since it is + * difficult to know object/array sizes ahead of time. + */ + +#include +#include +#include +#include "lua.h" +#include "lauxlib.h" + +#include "strbuf.h" + +#ifdef MISSING_ISINF +#define isinf(x) (!isnan(x) && isnan((x) - (x))) +#endif + +#define DEFAULT_SPARSE_CONVERT 0 +#define DEFAULT_SPARSE_RATIO 2 +#define DEFAULT_SPARSE_SAFE 10 +#define DEFAULT_MAX_DEPTH 20 +#define DEFAULT_ENCODE_REFUSE_BADNUM 1 +#define DEFAULT_DECODE_REFUSE_BADNUM 0 +#define DEFAULT_ENCODE_KEEP_BUFFER 1 + +typedef enum { + T_OBJ_BEGIN, + T_OBJ_END, + T_ARR_BEGIN, + T_ARR_END, + T_STRING, + T_NUMBER, + T_BOOLEAN, + T_NULL, + T_COLON, + T_COMMA, + T_END, + T_WHITESPACE, + T_ERROR, + T_UNKNOWN +} json_token_type_t; + +static const char *json_token_type_name[] = { + "T_OBJ_BEGIN", + "T_OBJ_END", + "T_ARR_BEGIN", + "T_ARR_END", + "T_STRING", + "T_NUMBER", + "T_BOOLEAN", + "T_NULL", + "T_COLON", + "T_COMMA", + "T_END", + "T_WHITESPACE", + "T_ERROR", + "T_UNKNOWN", + NULL +}; + +typedef struct { + json_token_type_t ch2token[256]; + char escape2char[256]; /* Decoding */ +#if 0 + char escapes[35][8]; /* Pre-generated escape string buffer */ + char *char2escape[256]; /* Encoding */ +#endif + strbuf_t encode_buf; + char number_fmt[8]; /* "%.XXg\0" */ + int current_depth; + + int encode_sparse_convert; + int encode_sparse_ratio; + int encode_sparse_safe; + int encode_max_depth; + int encode_refuse_badnum; + int decode_refuse_badnum; + int encode_keep_buffer; + int encode_number_precision; +} json_config_t; + +typedef struct { + const char *data; + int index; + strbuf_t *tmp; /* Temporary storage for strings */ + json_config_t *cfg; +} json_parse_t; + +typedef struct { + json_token_type_t type; + int index; + union { + const char *string; + double number; + int boolean; + } value; + int string_len; +} json_token_t; + +static const char *char2escape[256] = { + "\\u0000", "\\u0001", "\\u0002", "\\u0003", + "\\u0004", "\\u0005", "\\u0006", "\\u0007", + "\\b", "\\t", "\\n", "\\u000b", + "\\f", "\\r", "\\u000e", "\\u000f", + "\\u0010", "\\u0011", "\\u0012", "\\u0013", + "\\u0014", "\\u0015", "\\u0016", "\\u0017", + "\\u0018", "\\u0019", "\\u001a", "\\u001b", + "\\u001c", "\\u001d", "\\u001e", "\\u001f", + NULL, NULL, "\\\"", NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\/", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, "\\\\", NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\u007f", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +}; + +static int json_config_key; + +/* ===== CONFIGURATION ===== */ + +static json_config_t *json_fetch_config(lua_State *l) +{ + json_config_t *cfg; + + lua_pushlightuserdata(l, &json_config_key); + lua_gettable(l, LUA_REGISTRYINDEX); + cfg = lua_touserdata(l, -1); + if (!cfg) + luaL_error(l, "BUG: Unable to fetch CJSON configuration"); + + lua_pop(l, 1); + + return cfg; +} + +static void json_verify_arg_count(lua_State *l, int args) +{ + luaL_argcheck(l, lua_gettop(l) <= args, args + 1, + "found too many arguments"); +} + +/* Configures handling of extremely sparse arrays: + * convert: Convert extremely sparse arrays into objects? Otherwise error. + * ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio + * safe: Always use an array when the max index <= safe */ +static int json_cfg_encode_sparse_array(lua_State *l) +{ + json_config_t *cfg; + int val; + + json_verify_arg_count(l, 3); + cfg = json_fetch_config(l); + + switch (lua_gettop(l)) { + case 3: + val = luaL_checkinteger(l, 3); + luaL_argcheck(l, val >= 0, 3, "expected integer >= 0"); + cfg->encode_sparse_safe = val; + case 2: + val = luaL_checkinteger(l, 2); + luaL_argcheck(l, val >= 0, 2, "expected integer >= 0"); + cfg->encode_sparse_ratio = val; + case 1: + luaL_argcheck(l, lua_isboolean(l, 1), 1, "expected boolean"); + cfg->encode_sparse_convert = lua_toboolean(l, 1); + } + + lua_pushboolean(l, cfg->encode_sparse_convert); + lua_pushinteger(l, cfg->encode_sparse_ratio); + lua_pushinteger(l, cfg->encode_sparse_safe); + + return 3; +} + +/* Configures the maximum number of nested arrays/objects allowed when + * encoding */ +static int json_cfg_encode_max_depth(lua_State *l) +{ + json_config_t *cfg; + int depth; + + json_verify_arg_count(l, 1); + cfg = json_fetch_config(l); + + if (lua_gettop(l)) { + depth = luaL_checkinteger(l, 1); + luaL_argcheck(l, depth > 0, 1, "expected positive integer"); + cfg->encode_max_depth = depth; + } + + lua_pushinteger(l, cfg->encode_max_depth); + + return 1; +} + +static void json_set_number_precision(json_config_t *cfg, int prec) +{ + cfg->encode_number_precision = prec; + sprintf(cfg->number_fmt, "%%.%dg", prec); +} + +/* Configures number precision when converting doubles to text */ +static int json_cfg_encode_number_precision(lua_State *l) +{ + json_config_t *cfg; + int precision; + + json_verify_arg_count(l, 1); + cfg = json_fetch_config(l); + + if (lua_gettop(l)) { + precision = luaL_checkinteger(l, 1); + luaL_argcheck(l, 1 <= precision && precision <= 14, 1, + "expected integer between 1 and 14"); + json_set_number_precision(cfg, precision); + } + + lua_pushinteger(l, cfg->encode_number_precision); + + return 1; +} + +/* Configures JSON encoding buffer persistence */ +static int json_cfg_encode_keep_buffer(lua_State *l) +{ + json_config_t *cfg; + + json_verify_arg_count(l, 1); + cfg = json_fetch_config(l); + + if (lua_gettop(l)) { + luaL_checktype(l, 1, LUA_TBOOLEAN); + cfg->encode_keep_buffer = lua_toboolean(l, 1); + } + + lua_pushboolean(l, cfg->encode_keep_buffer); + + return 1; +} + +/* On argument: decode enum and set config variables + * **options must point to a NULL terminated array of 4 enums + * Returns: current enum value */ +static void json_enum_option(lua_State *l, const char **options, + int *opt1, int *opt2) +{ + int setting; + + if (lua_gettop(l)) { + if (lua_isboolean(l, 1)) + setting = lua_toboolean(l, 1) * 3; + else + setting = luaL_checkoption(l, 1, NULL, options); + + *opt1 = setting & 1 ? 1 : 0; + *opt2 = setting & 2 ? 1 : 0; + } else { + setting = *opt1 | (*opt2 << 1); + } + + if (setting) + lua_pushstring(l, options[setting]); + else + lua_pushboolean(l, 0); +} + + +/* When enabled, rejects: NaN, Infinity, hexidecimal numbers */ +static int json_cfg_refuse_invalid_numbers(lua_State *l) +{ + static const char *options_enc_dec[] = { "none", "encode", "decode", + "both", NULL }; + json_config_t *cfg; + + json_verify_arg_count(l, 1); + cfg = json_fetch_config(l); + + json_enum_option(l, options_enc_dec, + &cfg->encode_refuse_badnum, + &cfg->decode_refuse_badnum); + + return 1; +} + +static int json_destroy_config(lua_State *l) +{ + json_config_t *cfg; + + cfg = lua_touserdata(l, 1); + if (cfg) + strbuf_free(&cfg->encode_buf); + cfg = NULL; + + return 0; +} + +static void json_create_config(lua_State *l) +{ + json_config_t *cfg; + int i; + + cfg = lua_newuserdata(l, sizeof(*cfg)); + + /* Create GC method to clean up strbuf */ + lua_newtable(l); + lua_pushcfunction(l, json_destroy_config); + lua_setfield(l, -2, "__gc"); + lua_setmetatable(l, -2); + + strbuf_init(&cfg->encode_buf, 0); + + cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT; + cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO; + cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE; + cfg->encode_max_depth = DEFAULT_MAX_DEPTH; + cfg->encode_refuse_badnum = DEFAULT_ENCODE_REFUSE_BADNUM; + cfg->decode_refuse_badnum = DEFAULT_DECODE_REFUSE_BADNUM; + cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER; + json_set_number_precision(cfg, 14); + + /* Decoding init */ + + /* Tag all characters as an error */ + for (i = 0; i < 256; i++) + cfg->ch2token[i] = T_ERROR; + + /* Set tokens that require no further processing */ + cfg->ch2token['{'] = T_OBJ_BEGIN; + cfg->ch2token['}'] = T_OBJ_END; + cfg->ch2token['['] = T_ARR_BEGIN; + cfg->ch2token[']'] = T_ARR_END; + cfg->ch2token[','] = T_COMMA; + cfg->ch2token[':'] = T_COLON; + cfg->ch2token['\0'] = T_END; + cfg->ch2token[' '] = T_WHITESPACE; + cfg->ch2token['\t'] = T_WHITESPACE; + cfg->ch2token['\n'] = T_WHITESPACE; + cfg->ch2token['\r'] = T_WHITESPACE; + + /* Update characters that require further processing */ + cfg->ch2token['f'] = T_UNKNOWN; /* false? */ + cfg->ch2token['i'] = T_UNKNOWN; /* inf, ininity? */ + cfg->ch2token['I'] = T_UNKNOWN; + cfg->ch2token['n'] = T_UNKNOWN; /* null, nan? */ + cfg->ch2token['N'] = T_UNKNOWN; + cfg->ch2token['t'] = T_UNKNOWN; /* true? */ + cfg->ch2token['"'] = T_UNKNOWN; /* string? */ + cfg->ch2token['+'] = T_UNKNOWN; /* number? */ + cfg->ch2token['-'] = T_UNKNOWN; + for (i = 0; i < 10; i++) + cfg->ch2token['0' + i] = T_UNKNOWN; + + /* Lookup table for parsing escape characters */ + for (i = 0; i < 256; i++) + cfg->escape2char[i] = 0; /* String error */ + cfg->escape2char['"'] = '"'; + cfg->escape2char['\\'] = '\\'; + cfg->escape2char['/'] = '/'; + cfg->escape2char['b'] = '\b'; + cfg->escape2char['t'] = '\t'; + cfg->escape2char['n'] = '\n'; + cfg->escape2char['f'] = '\f'; + cfg->escape2char['r'] = '\r'; + cfg->escape2char['u'] = 'u'; /* Unicode parsing required */ + + +#if 0 + /* Initialise separate storage for pre-generated escape codes. + * Escapes 0-31 map directly, 34, 92, 127 follow afterwards to + * save memory. */ + for (i = 0 ; i < 32; i++) + sprintf(cfg->escapes[i], "\\u%04x", i); + strcpy(cfg->escapes[8], "\b"); /* Override simpler escapes */ + strcpy(cfg->escapes[9], "\t"); + strcpy(cfg->escapes[10], "\n"); + strcpy(cfg->escapes[12], "\f"); + strcpy(cfg->escapes[13], "\r"); + strcpy(cfg->escapes[32], "\\\""); /* chr(34) */ + strcpy(cfg->escapes[33], "\\\\"); /* chr(92) */ + sprintf(cfg->escapes[34], "\\u%04x", 127); /* char(127) */ + + /* Initialise encoding escape lookup table */ + for (i = 0; i < 32; i++) + cfg->char2escape[i] = cfg->escapes[i]; + for (i = 32; i < 256; i++) + cfg->char2escape[i] = NULL; + cfg->char2escape[34] = cfg->escapes[32]; + cfg->char2escape[92] = cfg->escapes[33]; + cfg->char2escape[127] = cfg->escapes[34]; +#endif +} + +/* ===== ENCODING ===== */ + +static void json_encode_exception(lua_State *l, json_config_t *cfg, int lindex, + const char *reason) +{ + if (!cfg->encode_keep_buffer) + strbuf_free(&cfg->encode_buf); + luaL_error(l, "Cannot serialise %s: %s", + lua_typename(l, lua_type(l, lindex)), reason); +} + +/* json_append_string args: + * - lua_State + * - JSON strbuf + * - String (Lua stack index) + * + * Returns nothing. Doesn't remove string from Lua stack */ +static void json_append_string(lua_State *l, strbuf_t *json, int lindex) +{ + const char *escstr; + int i; + const char *str; + size_t len; + + str = lua_tolstring(l, lindex, &len); + + /* Worst case is len * 6 (all unicode escapes). + * This buffer is reused constantly for small strings + * If there are any excess pages, they won't be hit anyway. + * This gains ~5% speedup. */ + strbuf_ensure_empty_length(json, len * 6 + 2); + + strbuf_append_char_unsafe(json, '\"'); + for (i = 0; i < len; i++) { + escstr = char2escape[(unsigned char)str[i]]; + if (escstr) + strbuf_append_string(json, escstr); + else + strbuf_append_char_unsafe(json, str[i]); + } + strbuf_append_char_unsafe(json, '\"'); +} + +/* Find the size of the array on the top of the Lua stack + * -1 object (not a pure array) + * >=0 elements in array + */ +static int lua_array_length(lua_State *l, json_config_t *cfg) +{ + double k; + int max; + int items; + + max = 0; + items = 0; + + lua_pushnil(l); + /* table, startkey */ + while (lua_next(l, -2) != 0) { + /* table, key, value */ + if (lua_type(l, -2) == LUA_TNUMBER && + (k = lua_tonumber(l, -2))) { + /* Integer >= 1 ? */ + if (floor(k) == k && k >= 1) { + if (k > max) + max = k; + items++; + lua_pop(l, 1); + continue; + } + } + + /* Must not be an array (non integer key) */ + lua_pop(l, 2); + return -1; + } + + /* Encode excessively sparse arrays as objects (if enabled) */ + if (cfg->encode_sparse_ratio > 0 && + max > items * cfg->encode_sparse_ratio && + max > cfg->encode_sparse_safe) { + if (!cfg->encode_sparse_convert) + json_encode_exception(l, cfg, -1, "excessively sparse array"); + + return -1; + } + + return max; +} + +static void json_encode_descend(lua_State *l, json_config_t *cfg) +{ + cfg->current_depth++; + + if (cfg->current_depth > cfg->encode_max_depth) { + if (!cfg->encode_keep_buffer) + strbuf_free(&cfg->encode_buf); + luaL_error(l, "Cannot serialise, excessive nesting (%d)", + cfg->current_depth); + } +} + +static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json); + +/* json_append_array args: + * - lua_State + * - JSON strbuf + * - Size of passwd Lua array (top of stack) */ +static void json_append_array(lua_State *l, json_config_t *cfg, strbuf_t *json, + int array_length) +{ + int comma, i; + + json_encode_descend(l, cfg); + + strbuf_append_char(json, '['); + + comma = 0; + for (i = 1; i <= array_length; i++) { + if (comma) + strbuf_append_char(json, ','); + else + comma = 1; + + lua_rawgeti(l, -1, i); + json_append_data(l, cfg, json); + lua_pop(l, 1); + } + + strbuf_append_char(json, ']'); + + cfg->current_depth--; +} + +static void json_append_number(lua_State *l, strbuf_t *json, int index, + json_config_t *cfg) +{ + double num = lua_tonumber(l, index); + + if (cfg->encode_refuse_badnum && (isinf(num) || isnan(num))) + json_encode_exception(l, cfg, index, "must not be NaN or Inf"); + + /* Lowest double printed with %.14g is 21 characters long: + * -1.7976931348623e+308 + * + * Use 32 to include the \0, and a few extra just in case.. + */ + strbuf_append_fmt(json, 32, cfg->number_fmt, num); +} + +static void json_append_object(lua_State *l, json_config_t *cfg, + strbuf_t *json) +{ + int comma, keytype; + + json_encode_descend(l, cfg); + + /* Object */ + strbuf_append_char(json, '{'); + + lua_pushnil(l); + /* table, startkey */ + comma = 0; + while (lua_next(l, -2) != 0) { + if (comma) + strbuf_append_char(json, ','); + else + comma = 1; + + /* table, key, value */ + keytype = lua_type(l, -2); + if (keytype == LUA_TNUMBER) { + strbuf_append_char(json, '"'); + json_append_number(l, json, -2, cfg); + strbuf_append_mem(json, "\":", 2); + } else if (keytype == LUA_TSTRING) { + json_append_string(l, json, -2); + strbuf_append_char(json, ':'); + } else { + json_encode_exception(l, cfg, -2, + "table key must be a number or string"); + /* never returns */ + } + + /* table, key, value */ + json_append_data(l, cfg, json); + lua_pop(l, 1); + /* table, key */ + } + + strbuf_append_char(json, '}'); + + cfg->current_depth--; +} + +/* Serialise Lua data into JSON string. */ +static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json) +{ + int len; + + switch (lua_type(l, -1)) { + case LUA_TSTRING: + json_append_string(l, json, -1); + break; + case LUA_TNUMBER: + json_append_number(l, json, -1, cfg); + break; + case LUA_TBOOLEAN: + if (lua_toboolean(l, -1)) + strbuf_append_mem(json, "true", 4); + else + strbuf_append_mem(json, "false", 5); + break; + case LUA_TTABLE: + len = lua_array_length(l, cfg); + if (len > 0) + json_append_array(l, cfg, json, len); + else + json_append_object(l, cfg, json); + break; + case LUA_TNIL: + strbuf_append_mem(json, "null", 4); + break; + case LUA_TLIGHTUSERDATA: + if (lua_touserdata(l, -1) == NULL) { + strbuf_append_mem(json, "null", 4); + break; + } + default: + /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD, + * and LUA_TLIGHTUSERDATA) cannot be serialised */ + json_encode_exception(l, cfg, -1, "type not supported"); + /* never returns */ + } +} + +static int json_encode(lua_State *l) +{ + json_config_t *cfg; + char *json; + int len; + + /* Can't use json_verify_arg_count() since we need to ensure + * there is only 1 argument */ + luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); + + cfg = json_fetch_config(l); + cfg->current_depth = 0; + + /* Reset the persistent buffer if it exists. + * Otherwise allocate a new buffer. */ + if (strbuf_allocated(&cfg->encode_buf)) + strbuf_reset(&cfg->encode_buf); + else + strbuf_init(&cfg->encode_buf, 0); + + json_append_data(l, cfg, &cfg->encode_buf); + json = strbuf_string(&cfg->encode_buf, &len); + + lua_pushlstring(l, json, len); + + if (!cfg->encode_keep_buffer) + strbuf_free(&cfg->encode_buf); + + return 1; +} + +/* ===== DECODING ===== */ + +static void json_process_value(lua_State *l, json_parse_t *json, + json_token_t *token); + +static int hexdigit2int(char hex) +{ + if ('0' <= hex && hex <= '9') + return hex - '0'; + + /* Force lowercase */ + hex |= 0x20; + if ('a' <= hex && hex <= 'f') + return 10 + hex - 'a'; + + return -1; +} + +static int decode_hex4(const char *hex) +{ + int digit[4]; + int i; + + /* Convert ASCII hex digit to numeric digit + * Note: this returns an error for invalid hex digits, including + * NULL */ + for (i = 0; i < 4; i++) { + digit[i] = hexdigit2int(hex[i]); + if (digit[i] < 0) { + return -1; + } + } + + return (digit[0] << 12) + + (digit[1] << 8) + + (digit[2] << 4) + + digit[3]; +} + +/* Converts a Unicode codepoint to UTF-8. + * Returns UTF-8 string length, and up to 4 bytes in *utf8 */ +static int codepoint_to_utf8(char *utf8, int codepoint) +{ + /* 0xxxxxxx */ + if (codepoint <= 0x7F) { + utf8[0] = codepoint; + return 1; + } + + /* 110xxxxx 10xxxxxx */ + if (codepoint <= 0x7FF) { + utf8[0] = (codepoint >> 6) | 0xC0; + utf8[1] = (codepoint & 0x3F) | 0x80; + return 2; + } + + /* 1110xxxx 10xxxxxx 10xxxxxx */ + if (codepoint <= 0xFFFF) { + utf8[0] = (codepoint >> 12) | 0xE0; + utf8[1] = ((codepoint >> 6) & 0x3F) | 0x80; + utf8[2] = (codepoint & 0x3F) | 0x80; + return 3; + } + + /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint <= 0x1FFFFF) { + utf8[0] = (codepoint >> 18) | 0xF0; + utf8[1] = ((codepoint >> 12) & 0x3F) | 0x80; + utf8[2] = ((codepoint >> 6) & 0x3F) | 0x80; + utf8[3] = (codepoint & 0x3F) | 0x80; + return 4; + } + + return 0; +} + + +/* Called when index pointing to beginning of UTF-16 code escape: \uXXXX + * \u is guaranteed to exist, but the remaining hex characters may be + * missing. + * Translate to UTF-8 and append to temporary token string. + * Must advance index to the next character to be processed. + * Returns: 0 success + * -1 error + */ +static int json_append_unicode_escape(json_parse_t *json) +{ + char utf8[4]; /* Surrogate pairs require 4 UTF-8 bytes */ + int codepoint; + int surrogate_low; + int len; + int escape_len = 6; + + /* Fetch UTF-16 code unit */ + codepoint = decode_hex4(&json->data[json->index + 2]); + if (codepoint < 0) + return -1; + + /* UTF-16 surrogate pairs take the following 2 byte form: + * 11011 x yyyyyyyyyy + * When x = 0: y is the high 10 bits of the codepoint + * x = 1: y is the low 10 bits of the codepoint + * + * Check for a surrogate pair (high or low) */ + if ((codepoint & 0xF800) == 0xD800) { + /* Error if the 1st surrogate is not high */ + if (codepoint & 0x400) + return -1; + + /* Ensure the next code is a unicode escape */ + if (json->data[json->index + escape_len] != '\\' || + json->data[json->index + escape_len + 1] != 'u') { + return -1; + } + + /* Fetch the next codepoint */ + surrogate_low = decode_hex4(&json->data[json->index + 2 + escape_len]); + if (surrogate_low < 0) + return -1; + + /* Error if the 2nd code is not a low surrogate */ + if ((surrogate_low & 0xFC00) != 0xDC00) + return -1; + + /* Calculate Unicode codepoint */ + codepoint = (codepoint & 0x3FF) << 10; + surrogate_low &= 0x3FF; + codepoint = (codepoint | surrogate_low) + 0x10000; + escape_len = 12; + } + + /* Convert codepoint to UTF-8 */ + len = codepoint_to_utf8(utf8, codepoint); + if (!len) + return -1; + + /* Append bytes and advance parse index */ + strbuf_append_mem_unsafe(json->tmp, utf8, len); + json->index += escape_len; + + return 0; +} + +static void json_set_token_error(json_token_t *token, json_parse_t *json, + const char *errtype) +{ + token->type = T_ERROR; + token->index = json->index; + token->value.string = errtype; +} + +static void json_next_string_token(json_parse_t *json, json_token_t *token) +{ + char *escape2char = json->cfg->escape2char; + char ch; + + /* Caller must ensure a string is next */ + assert(json->data[json->index] == '"'); + + /* Skip " */ + json->index++; + + /* json->tmp is the temporary strbuf used to accumulate the + * decoded string value. */ + strbuf_reset(json->tmp); + while ((ch = json->data[json->index]) != '"') { + if (!ch) { + /* Premature end of the string */ + json_set_token_error(token, json, "unexpected end of string"); + return; + } + + /* Handle escapes */ + if (ch == '\\') { + /* Fetch escape character */ + ch = json->data[json->index + 1]; + + /* Translate escape code and append to tmp string */ + ch = escape2char[(unsigned char)ch]; + if (ch == 'u') { + if (json_append_unicode_escape(json) == 0) + continue; + + json_set_token_error(token, json, + "invalid unicode escape code"); + return; + } + if (!ch) { + json_set_token_error(token, json, "invalid escape code"); + return; + } + + /* Skip '\' */ + json->index++; + } + /* Append normal character or translated single character + * Unicode escapes are handled above */ + strbuf_append_char_unsafe(json->tmp, ch); + json->index++; + } + json->index++; /* Eat final quote (") */ + + strbuf_ensure_null(json->tmp); + + token->type = T_STRING; + token->value.string = strbuf_string(json->tmp, &token->string_len); +} + +/* JSON numbers should take the following form: + * -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)? + * + * json_next_number_token() uses strtod() which allows other forms: + * - numbers starting with '+' + * - NaN, -NaN, infinity, -infinity + * - hexidecimal numbers + * - numbers with leading zeros + * + * json_is_invalid_number() detects "numbers" which may pass strtod()'s + * error checking, but should not be allowed with strict JSON. + * + * json_is_invalid_number() may pass numbers which cause strtod() + * to generate an error. + */ +static int json_is_invalid_number(json_parse_t *json) +{ + int i = json->index; + + /* Reject numbers starting with + */ + if (json->data[i] == '+') + return 1; + + /* Skip minus sign if it exists */ + if (json->data[i] == '-') + i++; + + /* Reject numbers starting with 0x, or leading zeros */ + if (json->data[i] == '0') { + int ch2 = json->data[i + 1]; + + if ((ch2 | 0x20) == 'x' || /* Hex */ + ('0' <= ch2 && ch2 <= '9')) /* Leading zero */ + return 1; + + return 0; + } else if (json->data[i] <= '9') { + return 0; /* Ordinary number */ + } + + + /* Reject inf/nan */ + if (!strncasecmp(&json->data[i], "inf", 3)) + return 1; + if (!strncasecmp(&json->data[i], "nan", 3)) + return 1; + + /* Pass all other numbers which may still be invalid, but + * strtod() will catch them. */ + return 0; +} + +static void json_next_number_token(json_parse_t *json, json_token_t *token) +{ + const char *startptr; + char *endptr; + + token->type = T_NUMBER; + startptr = &json->data[json->index]; + token->value.number = strtod(&json->data[json->index], &endptr); + if (startptr == endptr) + json_set_token_error(token, json, "invalid number"); + else + json->index += endptr - startptr; /* Skip the processed number */ + + return; +} + +/* Fills in the token struct. + * T_STRING will return a pointer to the json_parse_t temporary string + * T_ERROR will leave the json->index pointer at the error. + */ +static void json_next_token(json_parse_t *json, json_token_t *token) +{ + json_token_type_t *ch2token = json->cfg->ch2token; + int ch; + + /* Eat whitespace. FIXME: UGLY */ + token->type = ch2token[(unsigned char)json->data[json->index]]; + while (token->type == T_WHITESPACE) + token->type = ch2token[(unsigned char)json->data[++json->index]]; + + token->index = json->index; + + /* Don't advance the pointer for an error or the end */ + if (token->type == T_ERROR) { + json_set_token_error(token, json, "invalid token"); + return; + } + + if (token->type == T_END) { + return; + } + + /* Found a known single character token, advance index and return */ + if (token->type != T_UNKNOWN) { + json->index++; + return; + } + + /* Process characters which triggered T_UNKNOWN */ + ch = json->data[json->index]; + + /* Must use strncmp() to match the front of the JSON string. + * JSON identifier must be lowercase. + * When strict_numbers if disabled, either case is allowed for + * Infinity/NaN (since we are no longer following the spec..) */ + if (ch == '"') { + json_next_string_token(json, token); + return; + } else if (ch == '-' || ('0' <= ch && ch <= '9')) { + if (json->cfg->decode_refuse_badnum && json_is_invalid_number(json)) { + json_set_token_error(token, json, "invalid number"); + return; + } + json_next_number_token(json, token); + return; + } else if (!strncmp(&json->data[json->index], "true", 4)) { + token->type = T_BOOLEAN; + token->value.boolean = 1; + json->index += 4; + return; + } else if (!strncmp(&json->data[json->index], "false", 5)) { + token->type = T_BOOLEAN; + token->value.boolean = 0; + json->index += 5; + return; + } else if (!strncmp(&json->data[json->index], "null", 4)) { + token->type = T_NULL; + json->index += 4; + return; + } else if (!json->cfg->decode_refuse_badnum && + json_is_invalid_number(json)) { + /* When refuse_badnum is disabled, only attempt to process + * numbers we know are invalid JSON (Inf, NaN, hex) + * This is required to generate an appropriate token error, + * otherwise all bad tokens will register as "invalid number" + */ + json_next_number_token(json, token); + return; + } + + /* Token starts with t/f/n but isn't recognised above. */ + json_set_token_error(token, json, "invalid token"); +} + +/* This function does not return. + * DO NOT CALL WITH DYNAMIC MEMORY ALLOCATED. + * The only supported exception is the temporary parser string + * json->tmp struct. + * json and token should exist on the stack somewhere. + * luaL_error() will long_jmp and release the stack */ +static void json_throw_parse_error(lua_State *l, json_parse_t *json, + const char *exp, json_token_t *token) +{ + const char *found; + + strbuf_free(json->tmp); + + if (token->type == T_ERROR) + found = token->value.string; + else + found = json_token_type_name[token->type]; + + /* Note: token->index is 0 based, display starting from 1 */ + luaL_error(l, "Expected %s but found %s at character %d", + exp, found, token->index + 1); +} + +static void json_decode_checkstack(lua_State *l, json_parse_t *json, int n) +{ + if (lua_checkstack(l, n)) + return; + + strbuf_free(json->tmp); + luaL_error(l, "Too many nested data structures"); +} + +static void json_parse_object_context(lua_State *l, json_parse_t *json) +{ + json_token_t token; + + /* 3 slots required: + * .., table, key, value */ + json_decode_checkstack(l, json, 3); + + lua_newtable(l); + + json_next_token(json, &token); + + /* Handle empty objects */ + if (token.type == T_OBJ_END) { + return; + } + + while (1) { + if (token.type != T_STRING) + json_throw_parse_error(l, json, "object key string", &token); + + /* Push key */ + lua_pushlstring(l, token.value.string, token.string_len); + + json_next_token(json, &token); + if (token.type != T_COLON) + json_throw_parse_error(l, json, "colon", &token); + + /* Fetch value */ + json_next_token(json, &token); + json_process_value(l, json, &token); + + /* Set key = value */ + lua_rawset(l, -3); + + json_next_token(json, &token); + + if (token.type == T_OBJ_END) + return; + + if (token.type != T_COMMA) + json_throw_parse_error(l, json, "comma or object end", &token); + + json_next_token(json, &token); + } +} + +/* Handle the array context */ +static void json_parse_array_context(lua_State *l, json_parse_t *json) +{ + json_token_t token; + int i; + + /* 2 slots required: + * .., table, value */ + json_decode_checkstack(l, json, 2); + + lua_newtable(l); + + json_next_token(json, &token); + + /* Handle empty arrays */ + if (token.type == T_ARR_END) + return; + + for (i = 1; ; i++) { + json_process_value(l, json, &token); + lua_rawseti(l, -2, i); /* arr[i] = value */ + + json_next_token(json, &token); + + if (token.type == T_ARR_END) + return; + + if (token.type != T_COMMA) + json_throw_parse_error(l, json, "comma or array end", &token); + + json_next_token(json, &token); + } +} + +/* Handle the "value" context */ +static void json_process_value(lua_State *l, json_parse_t *json, + json_token_t *token) +{ + switch (token->type) { + case T_STRING: + lua_pushlstring(l, token->value.string, token->string_len); + break;; + case T_NUMBER: + lua_pushnumber(l, token->value.number); + break;; + case T_BOOLEAN: + lua_pushboolean(l, token->value.boolean); + break;; + case T_OBJ_BEGIN: + json_parse_object_context(l, json); + break;; + case T_ARR_BEGIN: + json_parse_array_context(l, json); + break;; + case T_NULL: + /* In Lua, setting "t[k] = nil" will delete k from the table. + * Hence a NULL pointer lightuserdata object is used instead */ + lua_pushlightuserdata(l, NULL); + break;; + default: + json_throw_parse_error(l, json, "value", token); + } +} + +/* json_text must be null terminated string */ +static void lua_json_decode(lua_State *l, const char *json_text, int json_len) +{ + json_parse_t json; + json_token_t token; + + json.cfg = json_fetch_config(l); + json.data = json_text; + json.index = 0; + + /* Ensure the temporary buffer can hold the entire string. + * This means we no longer need to do length checks since the decoded + * string must be smaller than the entire json string */ + json.tmp = strbuf_new(json_len); + + json_next_token(&json, &token); + json_process_value(l, &json, &token); + + /* Ensure there is no more input left */ + json_next_token(&json, &token); + + if (token.type != T_END) + json_throw_parse_error(l, &json, "the end", &token); + + strbuf_free(json.tmp); +} + +static int json_decode(lua_State *l) +{ + const char *json; + size_t len; + + json_verify_arg_count(l, 1); + + json = luaL_checklstring(l, 1, &len); + + /* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3) + * + * CJSON can support any simple data type, hence only the first + * character is guaranteed to be ASCII (at worst: '"'). This is + * still enough to detect whether the wrong encoding is in use. */ + if (len >= 2 && (!json[0] || !json[1])) + luaL_error(l, "JSON parser does not support UTF-16 or UTF-32"); + + lua_json_decode(l, json, len); + + return 1; +} + +/* ===== INITIALISATION ===== */ + +int luaopen_cjson(lua_State *l) +{ + luaL_Reg reg[] = { + { "encode", json_encode }, + { "decode", json_decode }, + { "encode_sparse_array", json_cfg_encode_sparse_array }, + { "encode_max_depth", json_cfg_encode_max_depth }, + { "encode_number_precision", json_cfg_encode_number_precision }, + { "encode_keep_buffer", json_cfg_encode_keep_buffer }, + { "refuse_invalid_numbers", json_cfg_refuse_invalid_numbers }, + { NULL, NULL } + }; + + /* Use json_fetch_config as a pointer. + * It's faster than using a config string, and more unique */ + lua_pushlightuserdata(l, &json_config_key); + json_create_config(l); + lua_settable(l, LUA_REGISTRYINDEX); + + luaL_register(l, "cjson", reg); + + /* Set cjson.null */ + lua_pushlightuserdata(l, NULL); + lua_setfield(l, -2, "null"); + + /* Set cjson.version */ + lua_pushliteral(l, VERSION); + lua_setfield(l, -2, "version"); + + /* Return cjson table */ + return 1; +} + +/* vi:ai et sw=4 ts=4: + */ diff --git a/src/script.c b/src/script.c index 68a5d081..dda3c2d8 100644 --- a/src/script.c +++ b/src/script.c @@ -45,9 +45,25 @@ static const struct luaL_Reg threadlib[] = { { NULL, NULL } }; +/* + * load lua lib and call it + */ +void lua_load_lib(lua_State* L, const char* libname, lua_CFunction luafunc) +{ + lua_pushcfunction(L, luafunc); + lua_pushstring(L, libname); + lua_call(L, 1, 0); +} + +int(luaopen_cjson)(lua_State* L); + lua_State *script_create(char *file, char *url, char **headers) { lua_State *L = luaL_newstate(); luaL_openlibs(L); + + // cjson lib + lua_load_lib(L, "cjson", luaopen_cjson); + (void) luaL_dostring(L, "wrk = require \"wrk\""); luaL_newmetatable(L, "wrk.addr"); diff --git a/src/strbuf.c b/src/strbuf.c new file mode 100644 index 00000000..976925a8 --- /dev/null +++ b/src/strbuf.c @@ -0,0 +1,251 @@ +/* strbuf - string buffer routines + * + * Copyright (c) 2010-2011 Mark Pulford + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE 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. + */ + +#include +#include +#include +#include + +#include "strbuf.h" + +void die(const char *fmt, ...) +{ + va_list arg; + + va_start(arg, fmt); + vfprintf(stderr, fmt, arg); + va_end(arg); + fprintf(stderr, "\n"); + + exit(-1); +} + +void strbuf_init(strbuf_t *s, int len) +{ + int size; + + if (len <= 0) + size = STRBUF_DEFAULT_SIZE; + else + size = len + 1; /* \0 terminator */ + + s->buf = NULL; + s->size = size; + s->length = 0; + s->increment = STRBUF_DEFAULT_INCREMENT; + s->dynamic = 0; + s->reallocs = 0; + s->debug = 0; + + s->buf = malloc(size); + if (!s->buf) + die("Out of memory"); + + strbuf_ensure_null(s); +} + +strbuf_t *strbuf_new(int len) +{ + strbuf_t *s; + + s = malloc(sizeof(strbuf_t)); + if (!s) + die("Out of memory"); + + strbuf_init(s, len); + + /* Dynamic strbuf allocation / deallocation */ + s->dynamic = 1; + + return s; +} + +void strbuf_set_increment(strbuf_t *s, int increment) +{ + /* Increment > 0: Linear buffer growth rate + * Increment < -1: Exponential buffer growth rate */ + if (increment == 0 || increment == -1) + die("BUG: Invalid string increment"); + + s->increment = increment; +} + +static inline void debug_stats(strbuf_t *s) +{ + if (s->debug) { + fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n", + (long)s, s->reallocs, s->length, s->size); + } +} + +/* If strbuf_t has not been dynamically allocated, strbuf_free() can + * be called any number of times strbuf_init() */ +void strbuf_free(strbuf_t *s) +{ + debug_stats(s); + + if (s->buf) { + free(s->buf); + s->buf = NULL; + } + if (s->dynamic) + free(s); +} + +char *strbuf_free_to_string(strbuf_t *s, int *len) +{ + char *buf; + + debug_stats(s); + + strbuf_ensure_null(s); + + buf = s->buf; + if (len) + *len = s->length; + + if (s->dynamic) + free(s); + + return buf; +} + +static int calculate_new_size(strbuf_t *s, int len) +{ + int reqsize, newsize; + + if (len <= 0) + die("BUG: Invalid strbuf length requested"); + + /* Ensure there is room for optional NULL termination */ + reqsize = len + 1; + + /* If the user has requested to shrink the buffer, do it exactly */ + if (s->size > reqsize) + return reqsize; + + newsize = s->size; + if (s->increment < 0) { + /* Exponential sizing */ + while (newsize < reqsize) + newsize *= -s->increment; + } else { + /* Linear sizing */ + newsize = ((newsize + s->increment - 1) / s->increment) * s->increment; + } + + return newsize; +} + + +/* Ensure strbuf can handle a string length bytes long (ignoring NULL + * optional termination). */ +void strbuf_resize(strbuf_t *s, int len) +{ + int newsize; + + newsize = calculate_new_size(s, len); + + if (s->debug > 1) { + fprintf(stderr, "strbuf(%lx) resize: %d => %d\n", + (long)s, s->size, newsize); + } + + s->size = newsize; + s->buf = realloc(s->buf, s->size); + if (!s->buf) + die("Out of memory"); + s->reallocs++; +} + +void strbuf_append_string(strbuf_t *s, const char *str) +{ + int space, i; + + space = strbuf_empty_length(s); + + for (i = 0; str[i]; i++) { + if (space < 1) { + strbuf_resize(s, s->length + 1); + space = strbuf_empty_length(s); + } + + s->buf[s->length] = str[i]; + s->length++; + space--; + } +} + +/* strbuf_append_fmt() should only be used when an upper bound + * is known for the output string. */ +void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...) +{ + va_list arg; + int fmt_len; + + strbuf_ensure_empty_length(s, len); + + va_start(arg, fmt); + fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg); + va_end(arg); + + if (fmt_len < 0) + die("BUG: Unable to convert number"); /* This should never happen.. */ + + s->length += fmt_len; +} + +/* strbuf_append_fmt_retry() can be used when the there is no known + * upper bound for the output string. */ +void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...) +{ + va_list arg; + int fmt_len, try; + int empty_len; + + /* If the first attempt to append fails, resize the buffer appropriately + * and try again */ + for (try = 0; ; try++) { + va_start(arg, fmt); + /* Append the new formatted string */ + /* fmt_len is the length of the string required, excluding the + * trailing NULL */ + empty_len = strbuf_empty_length(s); + /* Add 1 since there is also space to store the terminating NULL. */ + fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg); + va_end(arg); + + if (fmt_len <= empty_len) + break; /* SUCCESS */ + if (try > 0) + die("BUG: length of formatted string changed"); + + strbuf_resize(s, s->length + fmt_len); + } + + s->length += fmt_len; +} + +/* vi:ai et sw=4 ts=4: + */ diff --git a/src/strbuf.h b/src/strbuf.h new file mode 100644 index 00000000..f856543a --- /dev/null +++ b/src/strbuf.h @@ -0,0 +1,142 @@ +/* strbuf - String buffer routines + * + * Copyright (c) 2010-2011 Mark Pulford + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE 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. + */ + +#include +#include + +/* Size: Total bytes allocated to *buf + * Length: String length, excluding optional NULL terminator. + * Increment: Allocation increments when resizing the string buffer. + * Dynamic: True if created via strbuf_new() + */ + +typedef struct { + char *buf; + int size; + int length; + int increment; + int dynamic; + int reallocs; + int debug; +} strbuf_t; + +#ifndef STRBUF_DEFAULT_SIZE +#define STRBUF_DEFAULT_SIZE 1023 +#endif +#ifndef STRBUF_DEFAULT_INCREMENT +#define STRBUF_DEFAULT_INCREMENT -2 +#endif + +/* Initialise */ +extern strbuf_t *strbuf_new(int len); +extern void strbuf_init(strbuf_t *s, int len); +extern void strbuf_set_increment(strbuf_t *s, int increment); + +/* Release */ +extern void strbuf_free(strbuf_t *s); +extern char *strbuf_free_to_string(strbuf_t *s, int *len); + +/* Management */ +extern void strbuf_resize(strbuf_t *s, int len); +static int strbuf_empty_length(strbuf_t *s); +static int strbuf_length(strbuf_t *s); +static char *strbuf_string(strbuf_t *s, int *len); +static void strbuf_ensure_empty_length(strbuf_t *s, int len); + +/* Update */ +extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...); +extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...); +static void strbuf_append_mem(strbuf_t *s, const char *c, int len); +extern void strbuf_append_string(strbuf_t *s, const char *str); +static void strbuf_append_char(strbuf_t *s, const char c); +static void strbuf_ensure_null(strbuf_t *s); + +/* Reset string for before use */ +static inline void strbuf_reset(strbuf_t *s) +{ + s->length = 0; +} + +static inline int strbuf_allocated(strbuf_t *s) +{ + return s->buf != NULL; +} + +/* Return bytes remaining in the string buffer + * Ensure there is space for a NULL terminator. */ +static inline int strbuf_empty_length(strbuf_t *s) +{ + return s->size - s->length - 1; +} + +static inline void strbuf_ensure_empty_length(strbuf_t *s, int len) +{ + if (len > strbuf_empty_length(s)) + strbuf_resize(s, s->length + len); +} + +static inline int strbuf_length(strbuf_t *s) +{ + return s->length; +} + +static inline void strbuf_append_char(strbuf_t *s, const char c) +{ + strbuf_ensure_empty_length(s, 1); + s->buf[s->length++] = c; +} + +static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c) +{ + s->buf[s->length++] = c; +} + +static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len) +{ + strbuf_ensure_empty_length(s, len); + memcpy(s->buf + s->length, c, len); + s->length += len; +} + +static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len) +{ + memcpy(s->buf + s->length, c, len); + s->length += len; +} + +static inline void strbuf_ensure_null(strbuf_t *s) +{ + s->buf[s->length] = 0; +} + +static inline char *strbuf_string(strbuf_t *s, int *len) +{ + if (len) + *len = s->length; + + return s->buf; +} + +/* vi:ai et sw=4 ts=4: + */ From 17d2c3123494f5a7f9ceb6b5f0f488d89d260deb Mon Sep 17 00:00:00 2001 From: xiaobiaozhao <52393536+xiaobiaozhao@users.noreply.github.com> Date: Sun, 3 Jan 2021 21:07:25 +0800 Subject: [PATCH 12/12] Create c-cpp.yml --- .github/workflows/c-cpp.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/c-cpp.yml diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml new file mode 100644 index 00000000..384b7fc1 --- /dev/null +++ b/.github/workflows/c-cpp.yml @@ -0,0 +1,17 @@ +name: C/C++ CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: make + run: make -j4