#include <cassert>
#include <cerrno>
#include <cinttypes>
#include <cstring>
#include <err.h>
#include <getopt.h>
#include "benchmark.hpp"
#include "clo.hpp"
#include "clo_vec.hpp"
#include "queue.h"
#include "scenario.hpp"
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif
typedef int (*clo_parse_fn)(struct benchmark_clo *clo, const char *arg,
struct clo_vec *clovec);
typedef int (*clo_parse_single_fn)(struct benchmark_clo *clo, const char *arg,
void *ptr);
typedef int (*clo_eval_range_fn)(struct benchmark_clo *clo, void *first,
void *step, void *last, char type,
struct clo_vec_vlist *vlist);
typedef const char *(*clo_str_fn)(struct benchmark_clo *clo, void *addr,
size_t size);
#define STR_BUFF_SIZE 1024
static char str_buff[STR_BUFF_SIZE];
static int
clo_parse_flag(struct benchmark_clo *clo, const char *arg,
struct clo_vec *clovec)
{
bool flag = true;
if (arg != NULL) {
if (strcmp(arg, "true") == 0)
flag = true;
else if (strcmp(arg, "false") == 0)
flag = false;
else
return -1;
}
return clo_vec_memcpy(clovec, clo->off, sizeof(flag), &flag);
}
static int
clo_parse_str(struct benchmark_clo *clo, const char *arg,
struct clo_vec *clovec)
{
struct clo_vec_vlist *vlist = clo_vec_vlist_alloc();
assert(vlist != NULL);
char *str = strdup(arg);
assert(str != NULL);
clo_vec_add_alloc(clovec, str);
char *next = strtok(str, ",");
while (next) {
clo_vec_vlist_add(vlist, &next, sizeof(next));
next = strtok(NULL, ",");
}
int ret = clo_vec_memcpy_list(clovec, clo->off, sizeof(str), vlist);
clo_vec_vlist_free(vlist);
return ret;
}
static int
is_oct(const char *arg, size_t len)
{
return (arg[0] == '0' || (len > 1 && arg[0] == '-' && arg[1] == '0'));
}
static int
is_hex(const char *arg, size_t len)
{
if (arg[0] == '-') {
arg++;
len--;
}
return (len > 2 && arg[0] == '0' && (arg[1] == 'x' || arg[1] == 'X'));
}
static int
parse_number_base(const char *arg, void *value, int s, int base)
{
char *end;
errno = 0;
if (s) {
int64_t *v = (int64_t *)value;
*v = strtoll(arg, &end, base);
} else {
uint64_t *v = (uint64_t *)value;
*v = strtoull(arg, &end, base);
}
if (errno || *end != '\0')
return -1;
return 0;
}
static int
parse_number(const char *arg, size_t len, void *value, int s, int base)
{
if ((base & CLO_INT_BASE_HEX) && is_hex(arg, len)) {
if (!parse_number_base(arg, value, s, 16))
return 0;
}
if ((base & CLO_INT_BASE_OCT) && is_oct(arg, len)) {
if (!parse_number_base(arg, value, s, 8))
return 0;
}
if (base & CLO_INT_BASE_DEC) {
if (!parse_number_base(arg, value, s, 10))
return 0;
}
return -1;
}
static int
clo_parse_single_int(struct benchmark_clo *clo, const char *arg, void *ptr)
{
int64_t value = 0;
size_t len = strlen(arg);
if (parse_number(arg, len, &value, 1, clo->type_int.base)) {
errno = EINVAL;
return -1;
}
int64_t tmax = ((int64_t)1 << (8 * clo->type_int.size - 1)) - 1;
int64_t tmin = -((int64_t)1 << (8 * clo->type_int.size - 1));
tmax = min(tmax, clo->type_int.max);
tmin = max(tmin, clo->type_int.min);
if (value > tmax || value < tmin) {
errno = ERANGE;
return -1;
}
memcpy(ptr, &value, clo->type_int.size);
return 0;
}
static int
clo_parse_single_uint(struct benchmark_clo *clo, const char *arg, void *ptr)
{
if (arg[0] == '-') {
errno = EINVAL;
return -1;
}
uint64_t value = 0;
size_t len = strlen(arg);
if (parse_number(arg, len, &value, 0, clo->type_uint.base)) {
errno = EINVAL;
return -1;
}
uint64_t tmax = ~0 >> (64 - 8 * clo->type_uint.size);
uint64_t tmin = 0;
tmax = min(tmax, clo->type_uint.max);
tmin = max(tmin, clo->type_uint.min);
if (value > tmax || value < tmin) {
errno = ERANGE;
return -1;
}
memcpy(ptr, &value, clo->type_uint.size);
return 0;
}
static int
clo_eval_range_uint(struct benchmark_clo *clo, void *first, void *step,
void *last, char type, struct clo_vec_vlist *vlist)
{
uint64_t curr = *(uint64_t *)first;
uint64_t l = *(uint64_t *)last;
int64_t s = *(int64_t *)step;
while (1) {
clo_vec_vlist_add(vlist, &curr, clo->type_uint.size);
switch (type) {
case '+':
curr += s;
if (curr > l)
return 0;
break;
case '-':
if (curr < (uint64_t)s)
return 0;
curr -= s;
if (curr < l)
return 0;
break;
case '*':
curr *= s;
if (curr > l)
return 0;
break;
case '/':
curr /= s;
if (curr < l)
return 0;
break;
default:
return -1;
}
}
return -1;
}
static int
clo_eval_range_int(struct benchmark_clo *clo, void *first, void *step,
void *last, char type, struct clo_vec_vlist *vlist)
{
int64_t curr = *(int64_t *)first;
int64_t l = *(int64_t *)last;
uint64_t s = *(uint64_t *)step;
while (1) {
clo_vec_vlist_add(vlist, &curr, clo->type_int.size);
switch (type) {
case '+':
curr += s;
if (curr > l)
return 0;
break;
case '-':
curr -= s;
if (curr < l)
return 0;
break;
case '*':
curr *= s;
if (curr > l)
return 0;
break;
case '/':
curr /= s;
if (curr < l)
return 0;
break;
default:
return -1;
}
}
return -1;
}
static int
clo_check_range_params(uint64_t step, char step_type)
{
switch (step_type) {
case '+':
case '-':
if (step == 0)
return -1;
break;
case '*':
case '/':
if (step == 0 || step == 1)
return -1;
break;
default:
return -1;
}
return 0;
}
static int
clo_parse_range(struct benchmark_clo *clo, const char *arg,
clo_parse_single_fn parse_single, clo_eval_range_fn eval_range,
struct clo_vec_vlist *vlist)
{
char *str_first = (char *)malloc(strlen(arg) + 1);
assert(str_first != NULL);
char *str_step = (char *)malloc(strlen(arg) + 1);
assert(str_step != NULL);
char step_type = '\0';
char *str_last = (char *)malloc(strlen(arg) + 1);
assert(str_last != NULL);
int ret = sscanf(arg, "%[^:]:%c%[^:]:%[^:]", str_first, &step_type,
str_step, str_last);
if (ret == 1) {
uint64_t value;
if (parse_single(clo, arg, &value)) {
ret = -1;
} else {
if (clo->type == CLO_TYPE_UINT)
clo_vec_vlist_add(vlist, &value,
clo->type_uint.size);
else
clo_vec_vlist_add(vlist, &value,
clo->type_int.size);
ret = 0;
}
} else if (ret == 4) {
uint64_t first = 0;
uint64_t last = 0;
uint64_t step = 0;
if (parse_single(clo, str_first, &first)) {
ret = -1;
goto out;
}
char *end;
errno = 0;
step = strtoull(str_step, &end, 10);
if (errno || !end || *end != '\0') {
ret = -1;
goto out;
}
if (parse_single(clo, str_last, &last)) {
ret = -1;
goto out;
}
if (clo_check_range_params(step, step_type)) {
ret = -1;
goto out;
}
if (eval_range(clo, &first, &step, &last, step_type, vlist)) {
ret = -1;
goto out;
}
ret = 0;
} else {
ret = -1;
}
out:
free(str_first);
free(str_step);
free(str_last);
return ret;
}
static int
clo_parse_ranges(struct benchmark_clo *clo, const char *arg,
struct clo_vec *clovec, clo_parse_single_fn parse_single,
clo_eval_range_fn eval_range)
{
struct clo_vec_vlist *vlist = clo_vec_vlist_alloc();
assert(vlist != NULL);
int ret = 0;
char *args = strdup(arg);
assert(args != NULL);
char *curr = args;
char *next;
while ((next = strchr(curr, ',')) != NULL) {
*next = '\0';
next++;
if ((ret = clo_parse_range(clo, curr, parse_single, eval_range,
vlist)))
goto out;
curr = next;
}
if ((ret = clo_parse_range(clo, curr, parse_single, eval_range, vlist)))
goto out;
if (clo->type == CLO_TYPE_UINT)
ret = clo_vec_memcpy_list(clovec, clo->off, clo->type_uint.size,
vlist);
else
ret = clo_vec_memcpy_list(clovec, clo->off, clo->type_int.size,
vlist);
out:
free(args);
clo_vec_vlist_free(vlist);
return ret;
}
static int
clo_parse_int(struct benchmark_clo *clo, const char *arg,
struct clo_vec *clovec)
{
return clo_parse_ranges(clo, arg, clovec, clo_parse_single_int,
clo_eval_range_int);
}
static int
clo_parse_uint(struct benchmark_clo *clo, const char *arg,
struct clo_vec *clovec)
{
return clo_parse_ranges(clo, arg, clovec, clo_parse_single_uint,
clo_eval_range_uint);
}
static const char *
clo_str_flag(struct benchmark_clo *clo, void *addr, size_t size)
{
if (clo->off + sizeof(bool) > size)
return NULL;
bool flag = *(bool *)((char *)addr + clo->off);
return flag ? "true" : "false";
}
static const char *
clo_str_str(struct benchmark_clo *clo, void *addr, size_t size)
{
if (clo->off + sizeof(char *) > size)
return NULL;
return *(char **)((char *)addr + clo->off);
}
static const char *
clo_str_int(struct benchmark_clo *clo, void *addr, size_t size)
{
if (clo->off + clo->type_int.size > size)
return NULL;
void *val = (char *)addr + clo->off;
int ret = 0;
switch (clo->type_int.size) {
case 1:
ret = snprintf(str_buff, STR_BUFF_SIZE, "%" PRId8,
*(int8_t *)val);
break;
case 2:
ret = snprintf(str_buff, STR_BUFF_SIZE, "%" PRId16,
*(int16_t *)val);
break;
case 4:
ret = snprintf(str_buff, STR_BUFF_SIZE, "%" PRId32,
*(int32_t *)val);
break;
case 8:
ret = snprintf(str_buff, STR_BUFF_SIZE, "%" PRId64,
*(int64_t *)val);
break;
default:
return NULL;
}
if (ret < 0)
return NULL;
return str_buff;
}
static const char *
clo_str_uint(struct benchmark_clo *clo, void *addr, size_t size)
{
if (clo->off + clo->type_uint.size > size)
return NULL;
void *val = (char *)addr + clo->off;
int ret = 0;
switch (clo->type_uint.size) {
case 1:
ret = snprintf(str_buff, STR_BUFF_SIZE, "%" PRIu8,
*(uint8_t *)val);
break;
case 2:
ret = snprintf(str_buff, STR_BUFF_SIZE, "%" PRIu16,
*(uint16_t *)val);
break;
case 4:
ret = snprintf(str_buff, STR_BUFF_SIZE, "%" PRIu32,
*(uint32_t *)val);
break;
case 8:
ret = snprintf(str_buff, STR_BUFF_SIZE, "%" PRIu64,
*(uint64_t *)val);
break;
default:
return NULL;
}
if (ret < 0)
return NULL;
return str_buff;
}
static clo_parse_fn clo_parse[CLO_TYPE_MAX] = {
clo_parse_flag,
clo_parse_str,
clo_parse_int,
clo_parse_uint,
};
static clo_str_fn clo_str[CLO_TYPE_MAX] = {
clo_str_flag,
clo_str_str,
clo_str_int,
clo_str_uint,
};
static struct benchmark_clo *
clo_get_by_short(struct benchmark_clo *clos, size_t nclo, char opt_short)
{
size_t i;
for (i = 0; i < nclo; i++) {
if (clos[i].opt_short == opt_short)
return &clos[i];
}
return NULL;
}
static struct benchmark_clo *
clo_get_by_long(struct benchmark_clo *clos, size_t nclo, const char *opt_long)
{
size_t i;
for (i = 0; i < nclo; i++) {
if (strcmp(clos[i].opt_long, opt_long) == 0)
return &clos[i];
}
return NULL;
}
static char *
clo_get_optstr(struct benchmark_clo *clos, size_t nclo)
{
size_t i;
char *optstr;
char *ptr;
size_t optstrlen = nclo * 2 + 1;
optstr = (char *)calloc(1, optstrlen);
assert(optstr != NULL);
ptr = optstr;
for (i = 0; i < nclo; i++) {
if (clos[i].opt_short) {
*(ptr++) = clos[i].opt_short;
if (clos[i].type != CLO_TYPE_FLAG)
*(ptr++) = ':';
}
}
return optstr;
}
static struct option *
clo_get_long_options(struct benchmark_clo *clos, size_t nclo)
{
size_t i;
struct option *options;
options = (struct option *)calloc(nclo + 1, sizeof(struct option));
assert(options != NULL);
for (i = 0; i < nclo; i++) {
options[i].name = clos[i].opt_long;
options[i].val = clos[i].opt_short;
if (clos[i].type == CLO_TYPE_FLAG) {
options[i].has_arg = no_argument;
} else {
options[i].has_arg = required_argument;
}
}
return options;
}
static int
clo_set_defaults(struct benchmark_clo *clos, size_t nclo,
struct clo_vec *clovec)
{
size_t i;
for (i = 0; i < nclo; i++) {
if (clos[i].used)
continue;
if (clos[i].def) {
if (clo_parse[clos[i].type](&clos[i], clos[i].def,
clovec))
return -1;
} else if (clos[i].type == CLO_TYPE_FLAG) {
if (clo_parse[clos[i].type](&clos[i], "false", clovec))
return -1;
} else {
printf("'%s' is required option\n", clos[i].opt_long);
return -1;
}
}
return 0;
}
int
benchmark_clo_parse(int argc, char *argv[], struct benchmark_clo *clos,
ssize_t nclos, struct clo_vec *clovec)
{
char *optstr;
struct option *options;
int ret = 0;
int opt;
int optindex;
optstr = clo_get_optstr(clos, nclos);
options = clo_get_long_options(clos, nclos);
while ((opt = getopt_long(argc, argv, optstr, options, &optindex)) !=
-1) {
struct benchmark_clo *clo = NULL;
if (opt) {
clo = clo_get_by_short(clos, nclos, opt);
} else {
assert(optindex < nclos);
clo = &clos[optindex];
}
if (!clo) {
ret = -1;
goto out;
}
assert(clo->type < CLO_TYPE_MAX);
ret = clo_parse[clo->type](clo, optarg, clovec);
if (ret)
goto out;
clo->used = optarg != NULL || clo->type == CLO_TYPE_FLAG;
}
if (optind < argc) {
fprintf(stderr, "Unknown option: %s\n", argv[optind]);
ret = -1;
goto out;
}
ret = clo_set_defaults(clos, nclos, clovec);
out:
free(options);
free(optstr);
if (ret)
errno = EINVAL;
return ret;
}
int
benchmark_clo_parse_scenario(struct scenario *scenario,
struct benchmark_clo *clos, size_t nclos,
struct clo_vec *clovec)
{
struct kv *kv;
FOREACH_KV(kv, scenario)
{
struct benchmark_clo *clo =
clo_get_by_long(clos, nclos, kv->key);
if (!clo) {
fprintf(stderr, "unrecognized option -- '%s'\n",
kv->key);
return -1;
}
assert(clo->type < CLO_TYPE_MAX);
if (clo_parse[clo->type](clo, kv->value, clovec)) {
fprintf(stderr, "parsing option -- '%s' failed\n",
kv->value);
return -1;
}
clo->used = 1;
}
return clo_set_defaults(clos, nclos, clovec);
}
int
benchmark_override_clos_in_scenario(struct scenario *scenario, int argc,
char *argv[], struct benchmark_clo *clos,
int nclos)
{
char *optstr;
struct option *options;
int ret = 0;
int opt;
int optindex;
const char *true_str = "true";
optstr = clo_get_optstr(clos, nclos);
options = clo_get_long_options(clos, nclos);
while ((opt = getopt_long(argc, argv, optstr, options, &optindex)) !=
-1) {
struct benchmark_clo *clo = NULL;
if (opt) {
clo = clo_get_by_short(clos, nclos, opt);
} else {
assert(optindex < nclos);
clo = &clos[optindex];
}
if (!clo) {
ret = -1;
goto out;
}
struct kv *kv = find_kv_in_scenario(clo->opt_long, scenario);
if (kv) {
if (optarg != NULL && clo->type != CLO_TYPE_FLAG) {
free(kv->value);
kv->value = strdup(optarg);
} else if (optarg == NULL &&
clo->type == CLO_TYPE_FLAG) {
free(kv->value);
kv->value = strdup(true_str);
} else {
ret = -1;
goto out;
}
} else {
if (optarg != NULL && clo->type != CLO_TYPE_FLAG) {
kv = kv_alloc(clo->opt_long, optarg);
TAILQ_INSERT_TAIL(&scenario->head, kv, next);
} else if (optarg == NULL &&
clo->type == CLO_TYPE_FLAG) {
kv = kv_alloc(clo->opt_long, true_str);
TAILQ_INSERT_TAIL(&scenario->head, kv, next);
} else {
ret = -1;
goto out;
}
}
}
if (optind < argc) {
fprintf(stderr, "Unknown option: %s\n", argv[optind]);
ret = -1;
goto out;
}
out:
free(options);
free(optstr);
if (ret)
errno = EINVAL;
return ret;
}
const char *
benchmark_clo_str(struct benchmark_clo *clo, void *args, size_t size)
{
assert(clo->type < CLO_TYPE_MAX);
return clo_str[clo->type](clo, args, size);
}
int
clo_get_scenarios(int argc, char *argv[], struct scenarios *available_scenarios,
struct scenarios *found_scenarios)
{
assert(argv != NULL);
assert(available_scenarios != NULL);
assert(found_scenarios != NULL);
if (argc <= 0) {
fprintf(stderr, "clo get scenarios, argc invalid value: %d\n",
argc);
return -1;
}
int tmp_argc = argc;
char **tmp_argv = argv;
do {
struct scenario *scenario =
scenarios_get_scenario(available_scenarios, *tmp_argv);
if (!scenario) {
fprintf(stderr, "unknown scenario: %s\n", *tmp_argv);
return -1;
}
struct scenario *new_scenario = clone_scenario(scenario);
assert(new_scenario != NULL);
TAILQ_INSERT_TAIL(&found_scenarios->head, new_scenario, next);
tmp_argc--;
tmp_argv++;
} while (tmp_argc &&
contains_scenarios(tmp_argc, tmp_argv, available_scenarios));
return argc - tmp_argc;
}