-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added output as JSON #375
base: master
Are you sure you want to change the base?
Added output as JSON #375
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
obj/ | ||
wrk | ||
*.sw[pon] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ static struct config { | |
bool delay; | ||
bool dynamic; | ||
bool latency; | ||
bool json_format; | ||
char *host; | ||
char *script; | ||
SSL_CTX *ctx; | ||
|
@@ -51,6 +52,7 @@ static void usage() { | |
" -s, --script <S> Load Lua script file \n" | ||
" -H, --header <H> Add header to request \n" | ||
" --latency Print latency statistics \n" | ||
" -j --json-format Print output as JSON \n" | ||
" --timeout <T> 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) { | ||
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,81 @@ 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) { | ||
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(" \"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); | ||
} else { | ||
printf(" \"script\": null,\n"); | ||
} | ||
printf(" \"runtime_us\": %"PRIu64",\n", runtime_us); | ||
printf(" \"bytes\": %"PRIu64",\n", bytes); | ||
printf(" \"bytes_per_sec\": %Lf,\n", bytes_per_s); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JSON is for machine. Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Several reason, because:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't think JSON output should be same to original text output. Text is mainly for human. But JSON is more for machine. Too detailed information for human eye are omitted in text output, but it can be included in JSON. People expect backward compatibility about JSON format.
All of these reasons seems not enough for inclusion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @methane You don't say why it shouldn't be included. Otherwise, even you know the runtime and wrk knows the number of transfered bytes. The throughput is still a value researched by a lot of people. |
||
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); | ||
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_95\": %.2Lf,\n", lat_perc_95); | ||
printf(" \"latency_percentile_99\": %.2Lf,\n", lat_perc_99); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this really beautiful? How about
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Beautiful or not, I think it in several aspects:
I'm not against a new format, but let @wg decide about it. |
||
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 +529,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 +544,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 +566,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; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems not be escaped here. Is this
%s
secure?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by secure ?
If you think about code injection, this is just JSON. And a parser would expect only 1 object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If someone do this.
With
url
is"} || sudo rm /etc/passwd #
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will give:
So the ending quote is missing
Feel free to propose an enhancement 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please look my code
#
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which code are you talking about ?
If you're pointing me the ending
#
, the problem is still not here as the closing quote"
is not here. In interactive terminal, it will wait for you to close quotes. And in non-interactive, it will raise a syntax error.