427 lines
11 KiB
C
427 lines
11 KiB
C
/*
|
|
* Copyright 2019-2024 OARC, Inc.
|
|
* Copyright 2017-2018 Akamai Technologies
|
|
* Copyright 2006-2016 Nominum, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "opt.h"
|
|
|
|
#include "log.h"
|
|
#include "util.h"
|
|
#include "result.h"
|
|
|
|
#include <getopt.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <inttypes.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
#include <netinet/in.h>
|
|
|
|
#define MAX_OPTS 64
|
|
#define LINE_LENGTH 80
|
|
|
|
typedef struct {
|
|
char c;
|
|
perf_opttype_t type;
|
|
const char* desc;
|
|
const char* help;
|
|
const char* defval;
|
|
char defvalbuf[512];
|
|
union {
|
|
void* valp;
|
|
char** stringp;
|
|
bool* boolp;
|
|
unsigned int* uintp;
|
|
uint64_t* uint64p;
|
|
double* doublep;
|
|
in_port_t* portp;
|
|
} u;
|
|
} opt_t;
|
|
|
|
typedef struct long_opt long_opt_t;
|
|
struct long_opt {
|
|
long_opt_t* next;
|
|
const char* name;
|
|
perf_opttype_t type;
|
|
const char* desc;
|
|
const char* help;
|
|
const char* defval;
|
|
char defvalbuf[512];
|
|
union {
|
|
void* valp;
|
|
char** stringp;
|
|
bool* boolp;
|
|
unsigned int* uintp;
|
|
uint64_t* uint64p;
|
|
double* doublep;
|
|
in_port_t* portp;
|
|
} u;
|
|
};
|
|
|
|
static opt_t opts[MAX_OPTS];
|
|
static long_opt_t* longopts = 0;
|
|
static unsigned int nopts;
|
|
static char optstr[MAX_OPTS * 2 + 2 + 1] = { 0 };
|
|
extern const char* progname;
|
|
|
|
void perf_opt_add(char c, perf_opttype_t type, const char* desc, const char* help,
|
|
const char* defval, void* valp)
|
|
{
|
|
opt_t* opt;
|
|
|
|
if (nopts == MAX_OPTS) {
|
|
perf_log_fatal("too many defined options");
|
|
return;
|
|
}
|
|
opt = &opts[nopts++];
|
|
opt->c = c;
|
|
opt->type = type;
|
|
opt->desc = desc;
|
|
opt->help = help;
|
|
if (defval != NULL) {
|
|
if (strlen(defval) > sizeof(opt->defvalbuf) - 1) {
|
|
perf_log_fatal("perf_opt_add(): defval too large");
|
|
return;
|
|
}
|
|
strncpy(opt->defvalbuf, defval, sizeof(opt->defvalbuf));
|
|
opt->defvalbuf[sizeof(opt->defvalbuf) - 1] = 0;
|
|
opt->defval = opt->defvalbuf;
|
|
} else {
|
|
opt->defval = NULL;
|
|
}
|
|
opt->u.valp = valp;
|
|
|
|
char newoptstr[sizeof(optstr) + 2];
|
|
snprintf(newoptstr, sizeof(newoptstr), "%s%c%s", optstr, c, (type == perf_opt_boolean ? "" : ":"));
|
|
memcpy(optstr, newoptstr, sizeof(optstr) - 1);
|
|
optstr[sizeof(optstr) - 1] = 0;
|
|
}
|
|
|
|
void perf_long_opt_add(const char* name, perf_opttype_t type, const char* desc, const char* help, const char* defval, void* valp)
|
|
{
|
|
long_opt_t* opt = calloc(1, sizeof(long_opt_t));
|
|
|
|
if (!opt) {
|
|
perf_log_fatal("perf_long_opt_add(): out of memory");
|
|
return;
|
|
}
|
|
|
|
opt->name = name;
|
|
opt->type = type;
|
|
opt->desc = desc;
|
|
opt->help = help;
|
|
if (defval != NULL) {
|
|
if (strlen(defval) > sizeof(opt->defvalbuf) - 1) {
|
|
perf_log_fatal("perf_opt_add(): defval too large");
|
|
free(opt); // fix clang scan-build
|
|
return;
|
|
}
|
|
strncpy(opt->defvalbuf, defval, sizeof(opt->defvalbuf));
|
|
opt->defvalbuf[sizeof(opt->defvalbuf) - 1] = 0;
|
|
opt->defval = opt->defvalbuf;
|
|
} else {
|
|
opt->defval = NULL;
|
|
}
|
|
opt->u.valp = valp;
|
|
|
|
opt->next = longopts;
|
|
longopts = opt;
|
|
}
|
|
|
|
void perf_opt_usage(void)
|
|
{
|
|
unsigned int prefix_len, position, arg_len, i, j;
|
|
|
|
prefix_len = fprintf(stderr, "Usage: %s", progname);
|
|
position = prefix_len;
|
|
for (i = 0; i < nopts; i++) {
|
|
arg_len = 6;
|
|
if (opts[i].desc != NULL)
|
|
arg_len += strlen(opts[i].desc) + 1;
|
|
if (LINE_LENGTH - position - 1 < arg_len) {
|
|
fprintf(stderr, "\n");
|
|
for (j = 0; j < prefix_len; j++)
|
|
fprintf(stderr, " ");
|
|
position = prefix_len;
|
|
}
|
|
fprintf(stderr, " [-%c", opts[i].c);
|
|
if (opts[i].desc != NULL)
|
|
fprintf(stderr, " %s", opts[i].desc);
|
|
fprintf(stderr, "]");
|
|
position += arg_len;
|
|
}
|
|
fprintf(stderr, "\n");
|
|
|
|
for (i = 0; i < nopts; i++) {
|
|
fprintf(stderr, " -%c %s", opts[i].c, opts[i].help);
|
|
if (opts[i].defval)
|
|
fprintf(stderr, " (default: %s)", opts[i].defval);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
}
|
|
|
|
static uint32_t
|
|
parse_uint(const char* desc, const char* str,
|
|
unsigned int min, unsigned int max)
|
|
{
|
|
unsigned long int val;
|
|
uint32_t ret;
|
|
char* endptr = 0;
|
|
|
|
errno = 0;
|
|
val = strtoul(str, &endptr, 10);
|
|
if (!errno && str && *str && endptr && !*endptr && val <= UINT32_MAX) {
|
|
ret = (uint32_t)val;
|
|
if (ret >= min && ret <= max) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "invalid %s: %s\n", desc, str);
|
|
perf_opt_usage();
|
|
exit(1);
|
|
}
|
|
|
|
static double
|
|
parse_double(const char* desc, const char* str)
|
|
{
|
|
const char* s;
|
|
char c;
|
|
bool seen_dot = false;
|
|
|
|
s = str;
|
|
while (*s != 0) {
|
|
c = *s++;
|
|
if (c == '.') {
|
|
if (seen_dot)
|
|
goto fail;
|
|
seen_dot = true;
|
|
} else if (c < '0' || c > '9') {
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
return atof(str);
|
|
|
|
fail:
|
|
fprintf(stderr, "invalid %s: %s\n", desc, str);
|
|
perf_opt_usage();
|
|
exit(1);
|
|
}
|
|
|
|
static uint64_t
|
|
parse_timeval(const char* desc, const char* str)
|
|
{
|
|
return MILLION * parse_double(desc, str);
|
|
}
|
|
|
|
static int perf_opt_long_parse(char* optarg)
|
|
{
|
|
ssize_t optlen;
|
|
char* arg;
|
|
|
|
if ((arg = strchr(optarg, '='))) {
|
|
optlen = arg - optarg;
|
|
arg++;
|
|
if (optlen < 1 || !strlen(arg)) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
optlen = strlen(optarg);
|
|
}
|
|
|
|
long_opt_t* opt = longopts;
|
|
while (opt) {
|
|
if (!strncmp(optarg, opt->name, optlen)) {
|
|
switch (opt->type) {
|
|
case perf_opt_string:
|
|
if (!arg) {
|
|
return -1;
|
|
}
|
|
*opt->u.stringp = arg;
|
|
break;
|
|
case perf_opt_boolean:
|
|
*opt->u.boolp = true;
|
|
break;
|
|
case perf_opt_uint:
|
|
if (!arg) {
|
|
return -1;
|
|
}
|
|
*opt->u.uintp = parse_uint(opt->desc, arg, 1, 0xFFFFFFFF);
|
|
break;
|
|
case perf_opt_zpint:
|
|
if (!arg) {
|
|
return -1;
|
|
}
|
|
*opt->u.uintp = parse_uint(opt->desc, arg, 0, 0xFFFFFFFF);
|
|
break;
|
|
case perf_opt_timeval:
|
|
if (!arg) {
|
|
return -1;
|
|
}
|
|
*opt->u.uint64p = parse_timeval(opt->desc, arg);
|
|
break;
|
|
case perf_opt_double:
|
|
if (!arg) {
|
|
return -1;
|
|
}
|
|
*opt->u.doublep = parse_double(opt->desc, arg);
|
|
break;
|
|
case perf_opt_port:
|
|
if (!arg) {
|
|
return -1;
|
|
}
|
|
*opt->u.portp = parse_uint(opt->desc, arg, 0, 0xFFFF);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
opt = opt->next;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void perf_long_opt_usage(void)
|
|
{
|
|
fprintf(stderr, "Usage: %s ... -O <name>[=<value>] ...\n\nAvailable long options:\n", progname);
|
|
long_opt_t* opt = longopts;
|
|
while (opt) {
|
|
if (opt->type == perf_opt_boolean) {
|
|
fprintf(stderr, " %s: %s", opt->name, opt->help);
|
|
} else {
|
|
fprintf(stderr, " %s=<%s>: %s", opt->name, opt->desc ? opt->desc : "val", opt->help);
|
|
}
|
|
if (opt->defval) {
|
|
fprintf(stderr, " (default: %s)", opt->defval);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
|
|
opt = opt->next;
|
|
}
|
|
}
|
|
|
|
void perf_opt_parse(int argc, char** argv)
|
|
{
|
|
int c;
|
|
opt_t* opt;
|
|
unsigned int i;
|
|
|
|
perf_opt_add('h', perf_opt_boolean, NULL, "print this help", NULL, NULL);
|
|
perf_opt_add('H', perf_opt_boolean, NULL, "print long options help", NULL, NULL);
|
|
perf_opt_add('O', perf_opt_string, NULL, "set long options: <name>=<value>", NULL, NULL);
|
|
|
|
while ((c = getopt(argc, argv, optstr)) != -1) {
|
|
for (i = 0; i < nopts; i++) {
|
|
if (opts[i].c == c)
|
|
break;
|
|
}
|
|
if (i == nopts) {
|
|
perf_opt_usage();
|
|
exit(1);
|
|
}
|
|
if (c == 'h') {
|
|
perf_opt_usage();
|
|
exit(0);
|
|
}
|
|
if (c == 'H') {
|
|
perf_long_opt_usage();
|
|
exit(0);
|
|
}
|
|
if (c == 'O') {
|
|
if (perf_opt_long_parse(optarg)) {
|
|
fprintf(stderr, "invalid long option: %s\n", optarg);
|
|
perf_opt_usage();
|
|
exit(1);
|
|
}
|
|
continue;
|
|
}
|
|
opt = &opts[i];
|
|
switch (opt->type) {
|
|
case perf_opt_string:
|
|
*opt->u.stringp = optarg;
|
|
break;
|
|
case perf_opt_boolean:
|
|
*opt->u.boolp = true;
|
|
break;
|
|
case perf_opt_uint:
|
|
*opt->u.uintp = parse_uint(opt->desc, optarg,
|
|
1, 0xFFFFFFFF);
|
|
break;
|
|
case perf_opt_zpint:
|
|
*opt->u.uintp = parse_uint(opt->desc, optarg,
|
|
0, 0xFFFFFFFF);
|
|
break;
|
|
case perf_opt_timeval:
|
|
*opt->u.uint64p = parse_timeval(opt->desc, optarg);
|
|
break;
|
|
case perf_opt_double:
|
|
*opt->u.doublep = parse_double(opt->desc, optarg);
|
|
break;
|
|
case perf_opt_port:
|
|
*opt->u.portp = parse_uint(opt->desc, optarg,
|
|
0, 0xFFFF);
|
|
break;
|
|
}
|
|
}
|
|
if (optind != argc) {
|
|
fprintf(stderr, "unexpected argument %s\n", argv[optind]);
|
|
perf_opt_usage();
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
perf_suppress_t perf_opt_parse_suppress(const char* val)
|
|
{
|
|
perf_suppress_t s = { false, false, false, false };
|
|
|
|
while (val && *val) {
|
|
const char* next = strchr(val, ',');
|
|
int len;
|
|
if (next) {
|
|
len = next - val;
|
|
next += 1;
|
|
} else {
|
|
len = strlen(val);
|
|
next = 0;
|
|
}
|
|
|
|
if (!strncmp(val, "timeouts", len)) {
|
|
s.timeouts = true;
|
|
} else if (!strncmp(val, "congestion", len)) {
|
|
s.congestion = true;
|
|
} else if (!strncmp(val, "sendfailed", len)) {
|
|
s.sendfailed = true;
|
|
} else if (!strncmp(val, "sockready", len)) {
|
|
s.sockready = true;
|
|
} else if (!strncmp(val, "unexpected", len)) {
|
|
s.unexpected = true;
|
|
} else {
|
|
fprintf(stderr, "unknown message type to suppress: %.*s\n", len, val);
|
|
perf_opt_usage();
|
|
exit(1);
|
|
}
|
|
|
|
val = next;
|
|
}
|
|
|
|
return s;
|
|
}
|