From 7010c0332387eea98b52f301458d481f151840a6 Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Wed, 17 Jun 2015 14:07:58 -0400 Subject: [PATCH] Introduce utils_parse_time_suffix MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This function is based on utils_parse_size_suffix, but is to parse (relatively short) time suffixes. It returns the time in microseconds. So far, it supports: - u/us: microseconds, x1, same as no suffix - m/ms: milliseconds, x1 000 - s: seconds, x1 000 000 For example: - 32u becomes 32 - 32us becomes 32 - 32m becomes 32 000 - 32ms becomes 32 000 - 32s becomes 32 000 000 Signed-off-by: Simon Marchi Signed-off-by: Jérémie Galarneau --- src/bin/lttng/commands/enable_channels.c | 16 ++- src/common/utils.c | 101 ++++++++++++++++++ src/common/utils.h | 1 + tests/unit/Makefile.am | 12 ++- tests/unit/test_utils_parse_time_suffix.c | 121 ++++++++++++++++++++++ 5 files changed, 239 insertions(+), 12 deletions(-) create mode 100644 tests/unit/test_utils_parse_time_suffix.c diff --git a/src/bin/lttng/commands/enable_channels.c b/src/bin/lttng/commands/enable_channels.c index 9c84d0263..214c8a362 100644 --- a/src/bin/lttng/commands/enable_channels.c +++ b/src/bin/lttng/commands/enable_channels.c @@ -535,13 +535,13 @@ int cmd_enable_channels(int argc, const char **argv) } case OPT_MONITOR_TIMER: { - unsigned long long v; + uint64_t v; errno = 0; opt_arg = poptGetOptArg(pc); - v = strtoull(opt_arg, NULL, 0); - if (errno != 0 || !isdigit(opt_arg[0])) { - ERR("Wrong value in --monitor-timer parameter: %s", opt_arg); + + if (utils_parse_time_suffix(opt_arg, &v) < 0) { + ERR("Wrong value for --monitor-timer parameter: %s", opt_arg); ret = CMD_ERROR; goto end; } @@ -552,7 +552,7 @@ int cmd_enable_channels(int argc, const char **argv) } case OPT_BLOCKING_TIMEOUT: { - long long v; /* in usec */ + uint64_t v; long long v_msec; errno = 0; @@ -565,10 +565,8 @@ int cmd_enable_channels(int argc, const char **argv) break; } - v = strtoll(opt_arg, NULL, 0); - if (errno != 0 || (!isdigit(opt_arg[0]) && opt_arg[0] != '-') - || v < 0) { - ERR("Wrong value in --blocking-timeout parameter: %s", opt_arg); + if (utils_parse_time_suffix(opt_arg, &v) < 0) { + ERR("Wrong value for --blocking-timeout parameter: %s", opt_arg); ret = CMD_ERROR; goto end; } diff --git a/src/common/utils.c b/src/common/utils.c index f1e0148cb..004cd8f0d 100644 --- a/src/common/utils.c +++ b/src/common/utils.c @@ -1001,6 +1001,107 @@ end: return ret; } +/** + * Parse a string that represents a time in human readable format. It + * supports decimal integers suffixed by 's', 'u', 'm', 'us', and 'ms'. + * + * The suffix multiply the integer by: + * 'u'/'us': 1 + * 'm'/'ms': 1000 + * 's': 1000000 + * + * Note that unit-less numbers are assumed to be microseconds. + * + * @param str The string to parse, assumed to be NULL-terminated. + * @param time_us Pointer to a uint64_t that will be filled with the + * resulting time in microseconds. + * + * @return 0 on success, -1 on failure. + */ +LTTNG_HIDDEN +int utils_parse_time_suffix(char const * const str, uint64_t * const time_us) +{ + int ret; + uint64_t base_time; + long multiplier = 1; + const char *str_end; + char *num_end; + + if (!str) { + DBG("utils_parse_time_suffix: received a NULL string."); + ret = -1; + goto end; + } + + /* strtoull will accept a negative number, but we don't want to. */ + if (strchr(str, '-') != NULL) { + DBG("utils_parse_time_suffix: invalid time string, should not contain '-'."); + ret = -1; + goto end; + } + + /* str_end will point to the \0 */ + str_end = str + strlen(str); + errno = 0; + base_time = strtoull(str, &num_end, 10); + if (errno != 0) { + PERROR("utils_parse_time_suffix strtoull on string \"%s\"", str); + ret = -1; + goto end; + } + + if (num_end == str) { + /* strtoull parsed nothing, not good. */ + DBG("utils_parse_time_suffix: strtoull had nothing good to parse."); + ret = -1; + goto end; + } + + /* Check if a prefix is present. */ + switch (*num_end) { + case 'u': + multiplier = 1; + /* Skip another letter in the 'us' case. */ + num_end += (*(num_end + 1) == 's') ? 2 : 1; + break; + case 'm': + multiplier = 1000; + /* Skip another letter in the 'ms' case. */ + num_end += (*(num_end + 1) == 's') ? 2 : 1; + break; + case 's': + multiplier = 1000000; + num_end++; + break; + case '\0': + break; + default: + DBG("utils_parse_time_suffix: invalid suffix."); + ret = -1; + goto end; + } + + /* Check for garbage after the valid input. */ + if (num_end != str_end) { + DBG("utils_parse_time_suffix: Garbage after time string."); + ret = -1; + goto end; + } + + *time_us = base_time * multiplier; + + /* Check for overflow */ + if ((*time_us / multiplier) != base_time) { + DBG("utils_parse_time_suffix: oops, overflow detected."); + ret = -1; + goto end; + } + + ret = 0; +end: + return ret; +} + /* * fls: returns the position of the most significant bit. * Returns 0 if no bit is set, else returns the position of the most diff --git a/src/common/utils.h b/src/common/utils.h index 18f19ef1a..9293b1d0a 100644 --- a/src/common/utils.h +++ b/src/common/utils.h @@ -47,6 +47,7 @@ int utils_rotate_stream_file(char *path_name, char *file_name, uint64_t size, uint64_t count, int uid, int gid, int out_fd, uint64_t *new_count, int *stream_fd); int utils_parse_size_suffix(char const * const str, uint64_t * const size); +int utils_parse_time_suffix(char const * const str, uint64_t * const time_us); int utils_get_count_order_u32(uint32_t x); int utils_get_count_order_u64(uint64_t x); char *utils_get_home_dir(void); diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 340dd9365..1ff6ab1d0 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -10,6 +10,7 @@ TESTS = test_kernel_data \ test_session \ test_uri \ test_utils_parse_size_suffix \ + test_utils_parse_time_suffix \ test_utils_expand_path \ test_string_utils \ test_notification \ @@ -25,9 +26,9 @@ LIBRELAYD=$(top_builddir)/src/common/relayd/librelayd.la LIBLTTNG_CTL=$(top_builddir)/src/lib/lttng-ctl/liblttng-ctl.la # Define test programs -noinst_PROGRAMS = test_uri test_session test_kernel_data -noinst_PROGRAMS += test_utils_parse_size_suffix test_utils_expand_path -noinst_PROGRAMS += test_string_utils test_notification +noinst_PROGRAMS = test_uri test_session test_kernel_data \ + test_utils_parse_size_suffix test_utils_parse_time_suffix \ + test_utils_expand_path test_string_utils test_notification if HAVE_LIBLTTNG_UST_CTL noinst_PROGRAMS += test_ust_data @@ -103,6 +104,11 @@ test_utils_parse_size_suffix_SOURCES = test_utils_parse_size_suffix.c test_utils_parse_size_suffix_LDADD = $(LIBTAP) $(LIBHASHTABLE) $(LIBCOMMON) $(DL_LIBS) test_utils_parse_size_suffix_LDADD += $(UTILS_SUFFIX) +# parse_time_suffix unit test +test_utils_parse_time_suffix_SOURCES = test_utils_parse_time_suffix.c +test_utils_parse_time_suffix_LDADD = $(LIBTAP) $(LIBHASHTABLE) $(LIBCOMMON) +test_utils_parse_time_suffix_LDADD += $(UTILS_SUFFIX) + # expand_path unit test test_utils_expand_path_SOURCES = test_utils_expand_path.c test_utils_expand_path_LDADD = $(LIBTAP) $(LIBHASHTABLE) $(LIBCOMMON) $(DL_LIBS) diff --git a/tests/unit/test_utils_parse_time_suffix.c b/tests/unit/test_utils_parse_time_suffix.c new file mode 100644 index 000000000..96482769b --- /dev/null +++ b/tests/unit/test_utils_parse_time_suffix.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) - 2015 Simon Marchi + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by as + * published by the Free Software Foundation; only version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include + +#include + +/* For error.h */ +int lttng_opt_quiet = 1; +int lttng_opt_verbose = 3; +int lttng_opt_mi; + +struct valid_test_input { + char *input; + uint64_t expected_result; +}; + +/* Valid test cases */ +static struct valid_test_input valid_tests_inputs[] = { + { "0", 0 }, + { "1234", 1234 }, + { "0u", 0 }, + { "1234u", 1234 }, + { "16m", 16000 }, + { "128m", 128000 }, + { "32s", 32000000 }, + { "00", 0 }, + { "0m", 0 }, + { "0s", 0 }, + { "00m", 0 }, + { "00s", 0 }, + { "12ms", 12000 }, + { "3597us", 3597 }, + { "+5", 5 }, + { "08", 8 }, + { "0145us", 145 }, +}; +static const int num_valid_tests = sizeof(valid_tests_inputs) / sizeof(valid_tests_inputs[0]); + +/* Invalid test cases */ +static char *invalid_tests_inputs[] = { + "", + " ", + "-1", + "m", + "4611686018427387904s", + "0x40M", + "0x", + "x0", + "0xx0", + "07mm", + "0xm", + "0Xs", + "0x0ss", + "0a", + "0B", + "0x3 s", + "0xbs ", + "14ns", + "0xbs", + "14ns", + "14ms garbage after value", + "0x14s", +}; +static const int num_invalid_tests = sizeof(invalid_tests_inputs) / sizeof(invalid_tests_inputs[0]); + +static void test_utils_parse_time_suffix(void) +{ + uint64_t result; + int ret; + int i; + + /* Test valid cases */ + for (i = 0; i < num_valid_tests; i++) { + char name[100]; + + sprintf(name, "valid test case: %s", valid_tests_inputs[i].input); + + ret = utils_parse_time_suffix(valid_tests_inputs[i].input, &result); + ok(ret == 0 && result == valid_tests_inputs[i].expected_result, name); + } + + /* Test invalid cases */ + for (i = 0; i < num_invalid_tests; i++) { + char name[100]; + + sprintf(name, "invalid test case: %s", invalid_tests_inputs[i]); + + ret = utils_parse_time_suffix(invalid_tests_inputs[i], &result); + ok(ret != 0, name); + } +} + +int main(int argc, char **argv) +{ + plan_tests(num_valid_tests + num_invalid_tests); + + diag("utils_parse_time_suffix tests"); + + test_utils_parse_time_suffix(); + + return exit_status(); +} -- 2.34.1