Emit JSON on -J arg to demo #387

Accept -J as a parameter requiring an option to notcurses-demo(1).
Update the man page and usage message to reflect this. Open the
specified file for binary writing with truncation at option parsing
time; fail out if we can't open it. At the end of the demo, write
the results out as JSON, using full precision for all values #387.
This commit is contained in:
nick black 2020-02-27 03:27:54 -05:00
parent 87cac84db2
commit 0dc5456035
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC
2 changed files with 45 additions and 8 deletions

View File

@ -9,7 +9,7 @@ notcurses-demo - Show off some notcurses features
# SYNOPSIS
**notcurses-demo** [**-h|--help**] [**-p path**] [**-d delaymult**]
[**-l loglevel**] [**-f renderfile**] [**-ikVc**] demospec
[**-l loglevel**] [**-f renderfile**] [**-J jsonfile**] [**-ikVc**] demospec
# DESCRIPTION
@ -51,6 +51,8 @@ At any time, press 'q' to quit. The demo is best run in at least an 80x45 termin
**-f renderfile**: Render each frame to **renderfile** in addition to the screen.
**-J jsonfile**: Emit JSON summary of run to **jsonfile**.
**-k**: Inhibit use of the alternate screen. Necessary if you want the output left on your terminal after the program exits.
**-c**: Do not attempt to seed the PRNG. This is useful when benchmarking.

View File

@ -120,13 +120,15 @@ static struct {
static void
usage(const char* exe, int status){
FILE* out = status == EXIT_SUCCESS ? stdout : stderr;
fprintf(out, "usage: %s [ -hVkc ] [ -p path ] [ -l loglevel ] [ -d mult ] [ -f renderfile ] demospec\n", exe);
fprintf(out, "usage: %s [ -hVikc ] [ -p path ] [ -l loglevel ] [ -d mult ] [ -J jsonfile ] [ -f renderfile ] demospec\n", exe);
fprintf(out, " -h: this message\n");
fprintf(out, " -V: print program name and version\n");
fprintf(out, " -l: logging level (%d: silent..%d: manic)\n", NCLOGLEVEL_SILENT, NCLOGLEVEL_TRACE);
fprintf(out, " -i: ignore failures, keep going\n");
fprintf(out, " -k: keep screen; do not switch to alternate\n");
fprintf(out, " -d: delay multiplier (non-negative float)\n");
fprintf(out, " -f: render to file in addition to stdout\n");
fprintf(out, " -J: emit JSON summary to file\n");
fprintf(out, " -c: constant PRNG seed, useful for benchmarking\n");
fprintf(out, " -p: data file path (default: %s)\n", NOTCURSES_SHARE);
fprintf(out, "if no specification is provided, run %s\n", DEFAULT_DEMO);
@ -178,14 +180,16 @@ ext_demos(struct notcurses* nc, const char* spec, bool ignore_failures){
// specification, also returns NULL, heh. determine this by argv[optind];
// if it's NULL, there were valid options, but no spec.
static const char*
handle_opts(int argc, char** argv, notcurses_options* opts, bool* ignore_failures){
handle_opts(int argc, char** argv, notcurses_options* opts, bool* ignore_failures,
FILE** json_output){
strcpy(datadir, NOTCURSES_SHARE);
char renderfile[PATH_MAX] = "";
bool constant_seed = false;
*ignore_failures = false;
*json_output = NULL;
int c;
memset(opts, 0, sizeof(*opts));
while((c = getopt(argc, argv, "Vhickl:r:d:f:p:")) != EOF){
while((c = getopt(argc, argv, "VhickJ:l:r:d:f:p:")) != EOF){
switch(c){
case 'h':
usage(*argv, EXIT_SUCCESS);
@ -205,6 +209,16 @@ handle_opts(int argc, char** argv, notcurses_options* opts, bool* ignore_failure
}case 'V':
printf("notcurses-demo version %s\n", notcurses_version());
exit(EXIT_SUCCESS);
case 'J':
if(*json_output){
fprintf(stderr, "Supplied -J twice: %s\n", optarg);
usage(*argv, EXIT_FAILURE);
}
if((*json_output = fopen(optarg, "wb")) == NULL){
fprintf(stderr, "Error opening %s for JSON (%s?)\n", optarg, strerror(errno));
usage(*argv, EXIT_FAILURE);
}
break;
case 'c':
constant_seed = true;
break;
@ -288,6 +302,23 @@ table_printf(struct ncdirect* nc, const char* delim, const char* fmt, ...){
return 0;
}
static int
summary_json(FILE* f, const char* spec, int rows, int cols){
int ret = 0;
ret |= (fprintf(f, "{\"notcurses-demo\":{\"spec\":\"%s\",\"TERM\":\"%s\",\"rows\":\"%d\",\"cols\":\"%d\",\"runs\":{",
spec, getenv("TERM"), rows, cols) < 0);
for(size_t i = 0 ; i < strlen(spec) ; ++i){
if(results[i].result || !results[i].stats.renders){
continue;
}
ret |= (fprintf(f, "\"%s\":{\"bytes\":\"%ju\",\"frames\":\"%ju\",\"ns\":\"%ju\"}",
demos[results[i].selector - 'a'].name, results[i].stats.render_bytes,
results[i].stats.renders, results[i].timens) < 0);
}
ret |= (fprintf(f, "}}}\n") < 0);
return ret;
}
static int
summary_table(struct ncdirect* nc, const char* spec){
bool failed = false;
@ -348,7 +379,6 @@ summary_table(struct ncdirect* nc, const char* spec){
totalbytes += results[i].stats.render_bytes;
totalrenderns += results[i].stats.render_ns;
}
free(results);
qprefix(nsdelta, GIG, timebuf, 0);
bprefix(totalbytes, 1, totalbuf, 0);
qprefix(totalrenderns, GIG, rtimebuf, 0);
@ -384,9 +414,10 @@ int main(int argc, char** argv){
sigaddset(&sigmask, SIGWINCH);
pthread_sigmask(SIG_SETMASK, &sigmask, NULL);
const char* spec;
bool ignore_failures;
FILE* json = NULL; // emit JSON summary to this file? (-J)
bool ignore_failures; // continue after a failure? (-k)
notcurses_options nopts;
if((spec = handle_opts(argc, argv, &nopts, &ignore_failures)) == NULL){
if((spec = handle_opts(argc, argv, &nopts, &ignore_failures, &json)) == NULL){
if(argv[optind] != NULL){
usage(*argv, EXIT_FAILURE);
}
@ -458,14 +489,18 @@ int main(int argc, char** argv){
}
}
struct ncdirect* ncd = notcurses_directmode(NULL, stdout);
// reinitialize without alternate screen to do some coloring
if(!ncd){
return EXIT_FAILURE;
}
if(json && summary_json(json, spec, ncdirect_dim_y(ncd), ncdirect_dim_x(ncd))){
return EXIT_FAILURE;
}
// reinitialize without alternate screen to do some coloring
if(summary_table(ncd, spec)){
ncdirect_stop(ncd);
return EXIT_FAILURE;
}
free(results);
ncdirect_stop(ncd);
return EXIT_SUCCESS;