* [PATCH v1 1/1] app/testpmd: add sleep command
@ 2025-05-02 12:27 Anatoly Burakov
2025-05-02 12:37 ` Bruce Richardson
` (10 more replies)
0 siblings, 11 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-02 12:27 UTC (permalink / raw)
To: dev, Aman Singh
Test-pmd already has a way to run a list of commands from file, but there
is no way to pause execution for a specified amount of time between two
commands. This may be necessary for simple automation, particularly for
waiting on some asynchronous operation such as link status update.
Add a simple sleep command to wait until certain number of milliseconds has
passed.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index d4bb3ec998..1e429e6d0a 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
"quit\n"
" Quit to prompt.\n\n"
+
+ "sleep ms\n"
+ " Sleep for ms milliseconds.\n\n"
);
}
@@ -7768,6 +7771,37 @@ static cmdline_parse_inst_t cmd_quit = {
},
};
+/* *** SLEEP *** */
+struct cmd_sleep_result {
+ cmdline_fixed_string_t sleep;
+ uint32_t ms;
+};
+
+static void cmd_sleep_parsed(void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_sleep_result *res = parsed_result;
+
+ rte_delay_us_sleep(res->ms * 1000);
+}
+
+static cmdline_parse_token_string_t cmd_sleep_sleep =
+ TOKEN_STRING_INITIALIZER(struct cmd_sleep_result, sleep, "sleep");
+static cmdline_parse_token_num_t cmd_sleep_seconds =
+ TOKEN_NUM_INITIALIZER(struct cmd_sleep_result, ms, RTE_UINT32);
+
+static cmdline_parse_inst_t cmd_sleep = {
+ .f = cmd_sleep_parsed,
+ .data = NULL,
+ .help_str = "sleep <ms>: Sleep for a specified number of milliseconds",
+ .tokens = {
+ (void *)&cmd_sleep_sleep,
+ (void *)&cmd_sleep_seconds,
+ NULL,
+ },
+};
+
/* *** ADD/REMOVE MAC ADDRESS FROM A PORT *** */
struct cmd_mac_addr_result {
cmdline_fixed_string_t mac_addr_cmd;
@@ -13701,6 +13735,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_help_brief,
&cmd_help_long,
&cmd_quit,
+ &cmd_sleep,
&cmd_load_from_file,
&cmd_showport,
&cmd_showqueue,
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 1/1] app/testpmd: add sleep command
2025-05-02 12:27 [PATCH v1 1/1] app/testpmd: add sleep command Anatoly Burakov
@ 2025-05-02 12:37 ` Bruce Richardson
2025-05-02 14:35 ` Burakov, Anatoly
2025-05-02 15:42 ` Stephen Hemminger
` (9 subsequent siblings)
10 siblings, 1 reply; 45+ messages in thread
From: Bruce Richardson @ 2025-05-02 12:37 UTC (permalink / raw)
To: Anatoly Burakov; +Cc: dev, Aman Singh
On Fri, May 02, 2025 at 01:27:29PM +0100, Anatoly Burakov wrote:
> Test-pmd already has a way to run a list of commands from file, but there
> is no way to pause execution for a specified amount of time between two
> commands. This may be necessary for simple automation, particularly for
> waiting on some asynchronous operation such as link status update.
>
> Add a simple sleep command to wait until certain number of milliseconds has
> passed.
>
> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> ---
> app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
> 1 file changed, 35 insertions(+)
>
> diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
> index d4bb3ec998..1e429e6d0a 100644
> --- a/app/test-pmd/cmdline.c
> +++ b/app/test-pmd/cmdline.c
> @@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
>
> "quit\n"
> " Quit to prompt.\n\n"
> +
> + "sleep ms\n"
> + " Sleep for ms milliseconds.\n\n"
> );
> }
>
A "sleep" command, I would expect to sleep for a certain number of seconds,
for compatibility e.g. with terminal "sleep" command.
To keep this as "ms" granularity, how about making it explicit as a
"sleep_ms" command. Alternatively, how about adding a usleep command? [Not
sure "msleep" works, which is why I suggested sleep_ms instead]
/Bruce
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 1/1] app/testpmd: add sleep command
2025-05-02 12:37 ` Bruce Richardson
@ 2025-05-02 14:35 ` Burakov, Anatoly
2025-05-02 14:43 ` Bruce Richardson
0 siblings, 1 reply; 45+ messages in thread
From: Burakov, Anatoly @ 2025-05-02 14:35 UTC (permalink / raw)
To: Bruce Richardson; +Cc: dev, Aman Singh
On 5/2/2025 2:37 PM, Bruce Richardson wrote:
> On Fri, May 02, 2025 at 01:27:29PM +0100, Anatoly Burakov wrote:
>> Test-pmd already has a way to run a list of commands from file, but there
>> is no way to pause execution for a specified amount of time between two
>> commands. This may be necessary for simple automation, particularly for
>> waiting on some asynchronous operation such as link status update.
>>
>> Add a simple sleep command to wait until certain number of milliseconds has
>> passed.
>>
>> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
>> ---
>> app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
>> 1 file changed, 35 insertions(+)
>>
>> diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
>> index d4bb3ec998..1e429e6d0a 100644
>> --- a/app/test-pmd/cmdline.c
>> +++ b/app/test-pmd/cmdline.c
>> @@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
>>
>> "quit\n"
>> " Quit to prompt.\n\n"
>> +
>> + "sleep ms\n"
>> + " Sleep for ms milliseconds.\n\n"
>> );
>> }
>>
>
> A "sleep" command, I would expect to sleep for a certain number of seconds,
> for compatibility e.g. with terminal "sleep" command.
> To keep this as "ms" granularity, how about making it explicit as a
> "sleep_ms" command. Alternatively, how about adding a usleep command? [Not
> sure "msleep" works, which is why I suggested sleep_ms instead]
>
> /Bruce
I have no strong opinions on what it should do. My initial version *was*
a "sleep in seconds" command, I just thought that maybe someone would
want it more flexible. I suspect that actually "sleep" and second
granularity is just fine.
--
Thanks,
Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 1/1] app/testpmd: add sleep command
2025-05-02 14:35 ` Burakov, Anatoly
@ 2025-05-02 14:43 ` Bruce Richardson
2025-05-02 15:33 ` Morten Brørup
0 siblings, 1 reply; 45+ messages in thread
From: Bruce Richardson @ 2025-05-02 14:43 UTC (permalink / raw)
To: Burakov, Anatoly; +Cc: dev, Aman Singh
On Fri, May 02, 2025 at 04:35:08PM +0200, Burakov, Anatoly wrote:
> On 5/2/2025 2:37 PM, Bruce Richardson wrote:
> > On Fri, May 02, 2025 at 01:27:29PM +0100, Anatoly Burakov wrote:
> > > Test-pmd already has a way to run a list of commands from file, but there
> > > is no way to pause execution for a specified amount of time between two
> > > commands. This may be necessary for simple automation, particularly for
> > > waiting on some asynchronous operation such as link status update.
> > >
> > > Add a simple sleep command to wait until certain number of milliseconds has
> > > passed.
> > >
> > > Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> > > ---
> > > app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
> > > 1 file changed, 35 insertions(+)
> > >
> > > diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
> > > index d4bb3ec998..1e429e6d0a 100644
> > > --- a/app/test-pmd/cmdline.c
> > > +++ b/app/test-pmd/cmdline.c
> > > @@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
> > > "quit\n"
> > > " Quit to prompt.\n\n"
> > > +
> > > + "sleep ms\n"
> > > + " Sleep for ms milliseconds.\n\n"
> > > );
> > > }
> >
> > A "sleep" command, I would expect to sleep for a certain number of seconds,
> > for compatibility e.g. with terminal "sleep" command.
> > To keep this as "ms" granularity, how about making it explicit as a
> > "sleep_ms" command. Alternatively, how about adding a usleep command? [Not
> > sure "msleep" works, which is why I suggested sleep_ms instead]
> >
> > /Bruce
>
> I have no strong opinions on what it should do. My initial version *was* a
> "sleep in seconds" command, I just thought that maybe someone would want it
> more flexible. I suspect that actually "sleep" and second granularity is
> just fine.
>
I think we do need to support sub-second sleep granularity, though. If we
only add "sleep" with time specified in seconds, I think we'd also need to
add in usleep with micro-sec granularity too.
Other alternatives:
- have sleep take an option 3rd parameter of time in
minisecond. So "sleep 1" is to sleep for one second, but "sleep 0 500" is
to sleep for 1/2 sec, and "sleep 2 750" is to sleep for 2.75 seconds.
- add floating point support to the cmdline library, and then allow sleep time
specified in seconds using that.
/Bruce
^ permalink raw reply [flat|nested] 45+ messages in thread
* RE: [PATCH v1 1/1] app/testpmd: add sleep command
2025-05-02 14:43 ` Bruce Richardson
@ 2025-05-02 15:33 ` Morten Brørup
0 siblings, 0 replies; 45+ messages in thread
From: Morten Brørup @ 2025-05-02 15:33 UTC (permalink / raw)
To: Bruce Richardson, Burakov, Anatoly; +Cc: dev, Aman Singh
> From: Bruce Richardson [mailto:bruce.richardson@intel.com]
> Sent: Friday, 2 May 2025 16.44
>
> On Fri, May 02, 2025 at 04:35:08PM +0200, Burakov, Anatoly wrote:
> > On 5/2/2025 2:37 PM, Bruce Richardson wrote:
> > > On Fri, May 02, 2025 at 01:27:29PM +0100, Anatoly Burakov wrote:
> > > > Test-pmd already has a way to run a list of commands from file,
> but there
> > > > is no way to pause execution for a specified amount of time
> between two
> > > > commands. This may be necessary for simple automation,
> particularly for
> > > > waiting on some asynchronous operation such as link status
> update.
> > > >
> > > > Add a simple sleep command to wait until certain number of
> milliseconds has
> > > > passed.
> > > >
> > > > Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> > > > ---
> > > > app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
> > > > 1 file changed, 35 insertions(+)
> > > >
> > > > diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
> > > > index d4bb3ec998..1e429e6d0a 100644
> > > > --- a/app/test-pmd/cmdline.c
> > > > +++ b/app/test-pmd/cmdline.c
> > > > @@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void
> *parsed_result,
> > > > "quit\n"
> > > > " Quit to prompt.\n\n"
> > > > +
> > > > + "sleep ms\n"
> > > > + " Sleep for ms milliseconds.\n\n"
> > > > );
> > > > }
> > >
> > > A "sleep" command, I would expect to sleep for a certain number of
> seconds,
> > > for compatibility e.g. with terminal "sleep" command.
> > > To keep this as "ms" granularity, how about making it explicit as a
> > > "sleep_ms" command. Alternatively, how about adding a usleep
> command? [Not
> > > sure "msleep" works, which is why I suggested sleep_ms instead]
> > >
> > > /Bruce
> >
> > I have no strong opinions on what it should do. My initial version
> *was* a
> > "sleep in seconds" command, I just thought that maybe someone would
> want it
> > more flexible. I suspect that actually "sleep" and second granularity
> is
> > just fine.
> >
>
> I think we do need to support sub-second sleep granularity, though. If
> we
> only add "sleep" with time specified in seconds, I think we'd also need
> to
> add in usleep with micro-sec granularity too.
>
> Other alternatives:
> - have sleep take an option 3rd parameter of time in
> minisecond. So "sleep 1" is to sleep for one second, but "sleep 0
> 500" is
> to sleep for 1/2 sec, and "sleep 2 750" is to sleep for 2.75 seconds.
> - add floating point support to the cmdline library, and then allow
> sleep time
> specified in seconds using that.
>
> /Bruce
"sleep" should take one parameter: seconds.
It can be float, or a separate "usleep" command taking a microseconds parameter can be added. This change (using float instead of int) or addition (usleep command) can be added later, if you like.
Initially, I would lean towards a float parameter, but maybe it's easier for scripts to use "usleep" with an integer.
In real life, the duration will probably be either "N" (i.e. a natural number) or "0.N" (i.e. less than one second), so an "usleep" might be just as good.
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 1/1] app/testpmd: add sleep command
2025-05-02 12:27 [PATCH v1 1/1] app/testpmd: add sleep command Anatoly Burakov
2025-05-02 12:37 ` Bruce Richardson
@ 2025-05-02 15:42 ` Stephen Hemminger
2025-05-06 12:36 ` Burakov, Anatoly
2025-05-06 13:08 ` [PATCH v2 1/2] cmdline: add floating point support Anatoly Burakov
` (8 subsequent siblings)
10 siblings, 1 reply; 45+ messages in thread
From: Stephen Hemminger @ 2025-05-02 15:42 UTC (permalink / raw)
To: Anatoly Burakov; +Cc: dev, Aman Singh
On Fri, 2 May 2025 13:27:29 +0100
Anatoly Burakov <anatoly.burakov@intel.com> wrote:
> @@ -13701,6 +13735,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
> &cmd_help_brief,
> &cmd_help_long,
> &cmd_quit,
> + &cmd_sleep,
> &cmd_load_from_file,
> &cmd_showport,
> &cmd_showqueue,
> --
Looks like original list was in alphabetic order.
Please preserve that
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v1 1/1] app/testpmd: add sleep command
2025-05-02 15:42 ` Stephen Hemminger
@ 2025-05-06 12:36 ` Burakov, Anatoly
0 siblings, 0 replies; 45+ messages in thread
From: Burakov, Anatoly @ 2025-05-06 12:36 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev, Aman Singh
On 5/2/2025 5:42 PM, Stephen Hemminger wrote:
> On Fri, 2 May 2025 13:27:29 +0100
> Anatoly Burakov <anatoly.burakov@intel.com> wrote:
>
>> @@ -13701,6 +13735,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
>> &cmd_help_brief,
>> &cmd_help_long,
>> &cmd_quit,
>> + &cmd_sleep,
>> &cmd_load_from_file,
>> &cmd_showport,
>> &cmd_showqueue,
>> --
>
> Looks like original list was in alphabetic order.
> Please preserve that
It really wasn't, but I've moved it further down the list to reflect
help message ordering.
--
Thanks,
Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v2 1/2] cmdline: add floating point support
2025-05-02 12:27 [PATCH v1 1/1] app/testpmd: add sleep command Anatoly Burakov
2025-05-02 12:37 ` Bruce Richardson
2025-05-02 15:42 ` Stephen Hemminger
@ 2025-05-06 13:08 ` Anatoly Burakov
2025-05-06 13:08 ` [PATCH v2 2/2] app/testpmd: add sleep command Anatoly Burakov
2025-05-06 13:38 ` [PATCH v2 1/2] cmdline: add floating point support Bruce Richardson
2025-05-07 9:50 ` [PATCH v3 " Anatoly Burakov
` (7 subsequent siblings)
10 siblings, 2 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-06 13:08 UTC (permalink / raw)
To: dev
Add support for parsing floating point numbers in cmdline library, as well
as unit tests for the new functionality. The parser supports single and
double precision floats, and will understand decimal fractions as well as
scientific notation.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
app/test/test_cmdline_num.c | 201 +++++++++++++++++++++++-
lib/cmdline/cmdline_parse_num.c | 261 ++++++++++++++++++++++++++++++++
lib/cmdline/cmdline_parse_num.h | 4 +-
3 files changed, 462 insertions(+), 4 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index 9276de59bd..827ac06d9b 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -5,6 +5,8 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <rte_string_fns.h>
@@ -23,6 +25,11 @@ struct num_signed_str {
int64_t result;
};
+struct num_float_str {
+ const char * str;
+ double result;
+};
+
const struct num_unsigned_str num_valid_positive_strs[] = {
/* decimal positive */
{"0", 0 },
@@ -141,6 +148,63 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-9223372036854775808", INT64_MIN },
};
+const struct num_float_str num_valid_float_strs[] = {
+ /* zero */
+ {"0", 0},
+ /* parse int as float */
+ {"1", 1},
+ {"-1", -1},
+ /* fractional */
+ {"1.23", 1.23},
+ {"-1.23", -1.23},
+ {"0.123", 0.123},
+ {"-0.123", -0.123},
+ {"123.456", 123.456},
+ {"-123.456", -123.456},
+ /* positive exponent */
+ {"1e2", 1e2},
+ {"-1e2", -1e2},
+ {"1E2", 1E2},
+ {"-1E2", -1E2},
+ {"0.12e3", 0.12e3},
+ {"-0.12e3", -0.12e3},
+ {"1.23e4", 1.23e4},
+ {"-1.23e4", -1.23e4},
+ {"1.23E4", 1.23E4},
+ {"-1.23E4", -1.23E4},
+ {"123.456e7", 123.456e7},
+ {"-123.456e7", -123.456e7},
+ {"123.456E7", 123.456E7},
+ {"-123.456E7", -123.456E7},
+ /* negative exponent */
+ {"1e-2", 1e-2},
+ {"-1e-2", -1e-2},
+ {"1E-2", 1E-2},
+ {"-1E-2", -1E-2},
+ {"0.12e-3", 0.12e-3},
+ {"-0.12e-3", -0.12e-3},
+ {"1.23e-4", 1.23e-4},
+ {"-1.23e-4", -1.23e-4},
+ {"1.23E-4", 1.23E-4},
+ {"-1.23E-4", -1.23E-4},
+ {"123.456e-7", 123.456e-7},
+ {"-123.456e-7", -123.456e-7},
+ {"123.456E-7", 123.456E-7},
+ {"-123.456E-7", -123.456E-7},
+ /* try overflowing float */
+ {"2e63", 2e63},
+ {"-2e63", -2e63},
+ {"2E63", 2E63},
+ {"-2E63", -2E63},
+ {"18446744073709551615", (double) UINT64_MAX},
+ {"-9223372036854775808", (double) INT64_MIN},
+ /* try overflowing double */
+ {"2e308", HUGE_VAL},
+ {"-2e308", -HUGE_VAL},
+ {"2E308", HUGE_VAL},
+ {"-2E308", HUGE_VAL},
+};
+
const struct num_unsigned_str num_garbage_positive_strs[] = {
/* valid strings with garbage on the end, should still be valid */
/* decimal */
@@ -183,6 +247,28 @@ const struct num_signed_str num_garbage_negative_strs[] = {
{"-9223372036854775808 garbage", INT64_MIN },
};
+const char *float_invalid_strs[] = {
+ "0.",
+ ".1",
+ "1.1.",
+ "1.1.1",
+ "-0.",
+ "-.1",
+ "-1.1.",
+ "-1.1.1",
+ "e",
+ "1e",
+ "-1e",
+ "0.1e",
+ "-0.1e",
+ "1.e",
+ "-1.e",
+ "1.23e3.4",
+ "-1.23e3.4",
+ "1e1e",
+ "1e1e1"
+};
+
const char * num_invalid_strs[] = {
"18446744073709551616", /* out of range unsigned */
"-9223372036854775809", /* out of range negative signed */
@@ -202,7 +288,16 @@ const char * num_invalid_strs[] = {
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
"1111000011110000111100001111000011110000111100001111000011110000"),
+ /* valid float values but should fail to parse as ints */
"1E3",
+ "-1E3",
+ "1.23",
+ "-1.23",
+ "1E-3",
+ "-1E-3",
+ "1.23E4",
+ "-1.23E4",
+ /* misc invalid values */
"0A",
"-B",
"+4",
@@ -216,6 +311,47 @@ const char * num_invalid_strs[] = {
"\0",
};
+static int
+float_cmp(double expected, void *actual_p, enum cmdline_numtype type)
+{
+ double eps;
+ double actual_d;
+
+ if (type == RTE_FLOAT_SINGLE) {
+ /* read as float, convert to double */
+ actual_d = (double)*(float *)actual_p;
+ /* FLT_EPSILON is too small for some tests */
+ eps = 1e-5f;
+ } else {
+ /* read as double */
+ actual_d = *(double *)actual_p;
+ eps = DBL_EPSILON;
+ }
+ /* compare using epsilon value */
+ if (fabs(expected - actual_d) < eps)
+ return 0;
+ /* not equal */
+ return expected < actual_d ? -1 : 1;
+}
+
+static int
+can_parse_float(double expected_result, enum cmdline_numtype type)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ if (expected_result > FLT_MAX || expected_result < -FLT_MAX)
+ return 0;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (expected_result > DBL_MAX || expected_result < -DBL_MAX)
+ return 0;
+ break;
+ default:
+ return 1;
+ }
+ return 1;
+}
+
static int
can_parse_unsigned(uint64_t expected_result, enum cmdline_numtype type)
{
@@ -371,11 +507,11 @@ test_parse_num_invalid_data(void)
int ret = 0;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result; /* pick largest buffer */
cmdline_parse_token_num_t token;
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result; /* pick largest buffer */
token.num_data.type = type;
/* test full strings */
@@ -397,6 +533,31 @@ test_parse_num_invalid_data(void)
}
}
}
+
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result; /* pick largest buffer */
+ token.num_data.type = type;
+
+ /* test full strings */
+ for (i = 0; i < RTE_DIM(float_invalid_strs); i++) {
+
+ memset(&result, 0, sizeof(double));
+ memset(&buf, 0, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*)&token,
+ float_invalid_strs[i], (void*)&result, sizeof(result));
+ if (ret != -1) {
+ /* get some info about what we are trying to parse */
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ printf("Error: parsing %s as %s succeeded!\n",
+ float_invalid_strs[i], buf);
+ return -1;
+ }
+ }
+ }
return 0;
}
@@ -408,13 +569,13 @@ test_parse_num_valid(void)
enum cmdline_numtype type;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result;
cmdline_parse_token_num_t token;
/** valid strings **/
/* cycle through all possible parsed types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive strings */
@@ -489,10 +650,44 @@ test_parse_num_valid(void)
}
}
+ /* float values */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(num_valid_float_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ num_valid_float_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(num_valid_float_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ num_valid_float_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(num_valid_float_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ num_valid_float_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
/** garbage strings **/
/* cycle through all possible parsed types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive garbage strings */
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index f21796bedb..1799d10ce5 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -7,6 +7,8 @@
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <string.h>
#include <eal_export.h>
#include <rte_string_fns.h>
@@ -34,6 +36,9 @@ enum num_parse_state_t {
DEC_NEG,
BIN,
HEX,
+ FLOAT_POS,
+ FLOAT_NEG,
+ FLOAT_EXP,
ERROR,
@@ -44,12 +49,27 @@ enum num_parse_state_t {
BIN_OK,
DEC_NEG_OK,
DEC_POS_OK,
+ FLOAT_POS_OK,
+ FLOAT_NEG_OK,
+ FLOAT_EXP_POS_OK,
+ FLOAT_EXP_NEG_OK,
+};
+
+struct float_parse_state {
+ uint64_t dec;
+ uint64_t frac;
+ uint64_t frac_exp;
+ uint64_t exp;
+#define FLOAT_FLAG_NEG_RES (1 << 0)
+#define FLOAT_FLAG_NEG_EXP (1 << 1)
+ int flags;
};
/* Keep it sync with enum in .h */
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
"INT8", "INT16", "INT32", "INT64",
+ "SINGLE", "DOUBLE"
};
static inline int
@@ -63,6 +83,50 @@ add_to_res(unsigned int c, uint64_t *res, unsigned int base)
return 0;
}
+static inline int
+check_float_result(enum cmdline_numtype res_type, struct float_parse_state *fps,
+ void *res)
+{
+ double dec, frac, exp, result;
+
+ /* extract parts */
+ dec = (double) fps->dec;
+ frac = (double) fps->frac * pow(10.0, -(double)fps->frac_exp);
+ exp = (double) fps->exp;
+
+ /* exponent might be negative */
+ if (fps->flags & FLOAT_FLAG_NEG_EXP)
+ exp = pow(10.0, -exp);
+ else
+ exp = pow(10.0, exp);
+
+ /* combine decimal, fractional, and exponent parts */
+ result = (dec + frac) * exp;
+
+ /* check for any overflows */
+ if (isinf(frac) || isinf(exp) || isinf(result))
+ return -1;
+
+ /* result is a valid double */
+
+ /* check if result needs to be negative */
+ if (fps->flags & FLOAT_FLAG_NEG_RES)
+ result = -result;
+
+ if (res_type == RTE_FLOAT_SINGLE) {
+ /* float can overflow from conversion */
+ float flt = (float)result;
+ if (isinf(flt))
+ return -1;
+ if (res) *(float *)res = flt;
+ } else if (res_type == RTE_FLOAT_DOUBLE) {
+ if (res) *(double *)res = result;
+ } else {
+ return -1;
+ }
+ return 0;
+}
+
static int
check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
{
@@ -87,6 +151,14 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
if (ressize < sizeof(int64_t))
return -1;
break;
+ case RTE_FLOAT_SINGLE:
+ if (ressize < sizeof(float))
+ return -1;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (ressize < sizeof(double))
+ return -1;
+ break;
default:
return -1;
}
@@ -104,6 +176,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
const char * buf;
char c;
uint64_t res1 = 0;
+ struct float_parse_state fps = {};
if (!tk)
return -1;
@@ -156,6 +229,10 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
else
st = OCTAL_OK;
}
+ else if (c == '.') {
+ st = FLOAT_POS;
+ break;
+ }
else {
st = ERROR;
}
@@ -173,11 +250,68 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
}
break;
+ case FLOAT_POS:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else {
+ st = FLOAT_POS_OK;
+ fps.frac_exp++;
+ }
+ }
+ else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_NEG:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else {
+ st = FLOAT_NEG_OK;
+ fps.frac_exp++;
+ }
+ }
+ else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_EXP:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else
+ st = FLOAT_EXP_POS_OK;
+ }
+ else if (c == '-') {
+ st = FLOAT_EXP_NEG_OK;
+ fps.flags |= FLOAT_FLAG_NEG_EXP;
+ }
+ else {
+ st = ERROR;
+ }
+ break;
+
case DEC_NEG_OK:
if (c >= '0' && c <= '9') {
if (add_to_res(c - '0', &res1, 10) < 0)
st = ERROR;
}
+ else if (c == '.') {
+ fps.dec = res1;
+ fps.flags |= FLOAT_FLAG_NEG_RES;
+ st = FLOAT_NEG;
+ /* erase result */
+ res1 = 0;
+ } else if (c == 'e' || c == 'E') {
+ fps.dec = res1;
+ fps.flags |= FLOAT_FLAG_NEG_RES;
+ st = FLOAT_EXP;
+ /* erase result */
+ res1 = 0;
+ }
else {
st = ERROR;
}
@@ -188,11 +322,75 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
if (add_to_res(c - '0', &res1, 10) < 0)
st = ERROR;
}
+ else if (c == '.') {
+ fps.dec = res1;
+ st = FLOAT_POS;
+ /* erase result */
+ res1 = 0;
+ }
+ else if (c == 'e' || c == 'E') {
+ fps.dec = res1;
+ st = FLOAT_EXP;
+ /* erase result */
+ res1 = 0;
+ }
else {
st = ERROR;
}
break;
+ case FLOAT_POS_OK:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else
+ fps.frac_exp++;
+ } else if (c == 'e' || c == 'E') {
+ fps.frac = res1;
+ st = FLOAT_EXP;
+ /* erase result */
+ res1 = 0;
+ } else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_NEG_OK:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else
+ fps.frac_exp++;
+ } else if (c == 'e' || c == 'E') {
+ fps.frac = res1;
+ st = FLOAT_EXP;
+ /* erase result */
+ res1 = 0;
+ } else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_EXP_POS_OK:
+ /* exponent is always whole */
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ } else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_EXP_NEG_OK:
+ /* exponent is always whole */
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ } else {
+ st = ERROR;
+ }
+ break;
+
case HEX:
st = HEX_OK;
/* fall-through */
@@ -282,6 +480,12 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
} else if (nd.type == RTE_UINT64) {
if (res) *(uint64_t *)res = res1;
return buf-srcbuf;
+ } else if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ /* parsed double from integer */
+ fps.dec = res1;
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
} else {
return -1;
}
@@ -304,6 +508,63 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
res1 <= (uint64_t)INT64_MAX + 1) {
if (res) *(int64_t *)res = (int64_t) (-res1);
return buf-srcbuf;
+ } else if ((nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) &&
+ res1 <= (uint64_t)INT64_MAX + 1) {
+ /* parsed double from negative integer */
+ fps.dec = res1;
+ fps.flags |= FLOAT_FLAG_NEG_RES;
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
+ } else {
+ return -1;
+ }
+ break;
+
+ case FLOAT_POS_OK:
+ if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ fps.frac = res1;
+
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
+ } else {
+ return -1;
+ }
+ break;
+
+ case FLOAT_NEG_OK:
+ if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ fps.frac = res1;
+
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
+ } else {
+ return -1;
+ }
+ break;
+
+ case FLOAT_EXP_POS_OK:
+ /* watch for overflow in the exponent */
+ if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ fps.exp = res1;
+
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
+ } else {
+ return -1;
+ }
+ break;
+
+ case FLOAT_EXP_NEG_OK:
+ if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ fps.exp = res1;
+
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
} else {
return -1;
}
diff --git a/lib/cmdline/cmdline_parse_num.h b/lib/cmdline/cmdline_parse_num.h
index bdd0267612..b2792a2d11 100644
--- a/lib/cmdline/cmdline_parse_num.h
+++ b/lib/cmdline/cmdline_parse_num.h
@@ -22,7 +22,9 @@ enum cmdline_numtype {
RTE_INT8,
RTE_INT16,
RTE_INT32,
- RTE_INT64
+ RTE_INT64,
+ RTE_FLOAT_SINGLE,
+ RTE_FLOAT_DOUBLE,
};
struct cmdline_token_num_data {
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v2 2/2] app/testpmd: add sleep command
2025-05-06 13:08 ` [PATCH v2 1/2] cmdline: add floating point support Anatoly Burakov
@ 2025-05-06 13:08 ` Anatoly Burakov
2025-05-06 13:38 ` [PATCH v2 1/2] cmdline: add floating point support Bruce Richardson
1 sibling, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-06 13:08 UTC (permalink / raw)
To: dev, Aman Singh
Test-pmd already has a way to run a list of commands from file, but there
is no way to pause execution for a specified amount of time between two
commands. This may be necessary for simple automation, particularly for
waiting on some asynchronous operation such as link status update.
Add a simple sleep command to wait until certain number of seconds has
passed, using the newly added cmdline library floating point support.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v1 -> v2:
- Add floating point support to cmdline
- Use floating point format for pause command
app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index d4bb3ec998..b6152c07e6 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
"quit\n"
" Quit to prompt.\n\n"
+
+ "sleep secs\n"
+ " Sleep for secs seconds (can be fractional).\n\n"
);
}
@@ -7768,6 +7771,37 @@ static cmdline_parse_inst_t cmd_quit = {
},
};
+/* *** SLEEP *** */
+struct cmd_sleep_result {
+ cmdline_fixed_string_t sleep;
+ double secs;
+};
+
+static void cmd_sleep_parsed(void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_sleep_result *res = parsed_result;
+
+ rte_delay_us_sleep(res->secs * 1E6);
+}
+
+static cmdline_parse_token_string_t cmd_sleep_sleep =
+ TOKEN_STRING_INITIALIZER(struct cmd_sleep_result, sleep, "sleep");
+static cmdline_parse_token_num_t cmd_sleep_seconds =
+ TOKEN_NUM_INITIALIZER(struct cmd_sleep_result, secs, RTE_FLOAT_DOUBLE);
+
+static cmdline_parse_inst_t cmd_sleep = {
+ .f = cmd_sleep_parsed,
+ .data = NULL,
+ .help_str = "sleep <secs>: Sleep for a specified number of seconds",
+ .tokens = {
+ (void *)&cmd_sleep_sleep,
+ (void *)&cmd_sleep_seconds,
+ NULL,
+ },
+};
+
/* *** ADD/REMOVE MAC ADDRESS FROM A PORT *** */
struct cmd_mac_addr_result {
cmdline_fixed_string_t mac_addr_cmd;
@@ -13711,6 +13745,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_showdevice,
&cmd_showcfg,
&cmd_showfwdall,
+ &cmd_sleep,
&cmd_start,
&cmd_start_tx_first,
&cmd_start_tx_first_n,
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v2 1/2] cmdline: add floating point support
2025-05-06 13:08 ` [PATCH v2 1/2] cmdline: add floating point support Anatoly Burakov
2025-05-06 13:08 ` [PATCH v2 2/2] app/testpmd: add sleep command Anatoly Burakov
@ 2025-05-06 13:38 ` Bruce Richardson
2025-05-07 9:02 ` Burakov, Anatoly
1 sibling, 1 reply; 45+ messages in thread
From: Bruce Richardson @ 2025-05-06 13:38 UTC (permalink / raw)
To: Anatoly Burakov; +Cc: dev
On Tue, May 06, 2025 at 02:08:18PM +0100, Anatoly Burakov wrote:
> Add support for parsing floating point numbers in cmdline library, as well
> as unit tests for the new functionality. The parser supports single and
> double precision floats, and will understand decimal fractions as well as
> scientific notation.
>
> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> ---
> app/test/test_cmdline_num.c | 201 +++++++++++++++++++++++-
> lib/cmdline/cmdline_parse_num.c | 261 ++++++++++++++++++++++++++++++++
> lib/cmdline/cmdline_parse_num.h | 4 +-
> 3 files changed, 462 insertions(+), 4 deletions(-)
>
This is great to see, thanks. Can you also look to add support to
buildtools/dpdk-cmdline-gen.py script, to make it possible to use this from
a cmdline list file as with many of our sample apps?
/Bruce
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v2 1/2] cmdline: add floating point support
2025-05-06 13:38 ` [PATCH v2 1/2] cmdline: add floating point support Bruce Richardson
@ 2025-05-07 9:02 ` Burakov, Anatoly
0 siblings, 0 replies; 45+ messages in thread
From: Burakov, Anatoly @ 2025-05-07 9:02 UTC (permalink / raw)
To: Bruce Richardson; +Cc: dev
On 5/6/2025 3:38 PM, Bruce Richardson wrote:
> On Tue, May 06, 2025 at 02:08:18PM +0100, Anatoly Burakov wrote:
>> Add support for parsing floating point numbers in cmdline library, as well
>> as unit tests for the new functionality. The parser supports single and
>> double precision floats, and will understand decimal fractions as well as
>> scientific notation.
>>
>> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
>> ---
>> app/test/test_cmdline_num.c | 201 +++++++++++++++++++++++-
>> lib/cmdline/cmdline_parse_num.c | 261 ++++++++++++++++++++++++++++++++
>> lib/cmdline/cmdline_parse_num.h | 4 +-
>> 3 files changed, 462 insertions(+), 4 deletions(-)
>>
> This is great to see, thanks. Can you also look to add support to
> buildtools/dpdk-cmdline-gen.py script, to make it possible to use this from
> a cmdline list file as with many of our sample apps?
>
> /Bruce
Good call, will do!
(also probably needs a doc update somewhere, so i'll see if I can add
that too)
--
Thanks,
Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v3 1/2] cmdline: add floating point support
2025-05-02 12:27 [PATCH v1 1/1] app/testpmd: add sleep command Anatoly Burakov
` (2 preceding siblings ...)
2025-05-06 13:08 ` [PATCH v2 1/2] cmdline: add floating point support Anatoly Burakov
@ 2025-05-07 9:50 ` Anatoly Burakov
2025-05-07 9:50 ` [PATCH v3 2/2] app/testpmd: add sleep command Anatoly Burakov
2025-05-07 9:53 ` [PATCH v3 1/2] cmdline: add floating point support Burakov, Anatoly
2025-05-07 10:01 ` [PATCH v4 " Anatoly Burakov
` (6 subsequent siblings)
10 siblings, 2 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-07 9:50 UTC (permalink / raw)
To: dev
Add support for parsing floating point numbers in cmdline library, as well
as unit tests for the new functionality. The parser supports single and
double precision floats, and will understand decimal fractions as well as
scientific notation.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
app/test/test_cmdline_num.c | 203 +++++++++++++++++-
buildtools/dpdk-cmdline-gen.py | 24 ++-
doc/guides/prog_guide/cmdline.rst | 3 +
doc/guides/rel_notes/release_25_07.rst | 5 +
lib/cmdline/cmdline_parse_num.c | 274 +++++++++++++++++++++++++
lib/cmdline/cmdline_parse_num.h | 4 +-
6 files changed, 498 insertions(+), 15 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index 9276de59bd..e4038271c9 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -5,6 +5,8 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <rte_string_fns.h>
@@ -23,6 +25,11 @@ struct num_signed_str {
int64_t result;
};
+struct num_float_str {
+ const char * str;
+ double result;
+};
+
const struct num_unsigned_str num_valid_positive_strs[] = {
/* decimal positive */
{"0", 0 },
@@ -141,6 +148,63 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-9223372036854775808", INT64_MIN },
};
+const struct num_float_str num_valid_float_strs[] = {
+ /* zero */
+ {"0", 0},
+ /* parse int as float */
+ {"1", 1},
+ {"-1", -1},
+ /* fractional */
+ {"1.23", 1.23},
+ {"-1.23", -1.23},
+ {"0.123", 0.123},
+ {"-0.123", -0.123},
+ {"123.456", 123.456},
+ {"-123.456", -123.456},
+ /* positive exponent */
+ {"1e2", 1e2},
+ {"-1e2", -1e2},
+ {"1E2", 1E2},
+ {"-1E2", -1E2},
+ {"0.12e3", 0.12e3},
+ {"-0.12e3", -0.12e3},
+ {"1.23e4", 1.23e4},
+ {"-1.23e4", -1.23e4},
+ {"1.23E4", 1.23E4},
+ {"-1.23E4", -1.23E4},
+ {"123.456e7", 123.456e7},
+ {"-123.456e7", -123.456e7},
+ {"123.456E7", 123.456E7},
+ {"-123.456E7", -123.456E7},
+ /* negative exponent */
+ {"1e-2", 1e-2},
+ {"-1e-2", -1e-2},
+ {"1E-2", 1E-2},
+ {"-1E-2", -1E-2},
+ {"0.12e-3", 0.12e-3},
+ {"-0.12e-3", -0.12e-3},
+ {"1.23e-4", 1.23e-4},
+ {"-1.23e-4", -1.23e-4},
+ {"1.23E-4", 1.23E-4},
+ {"-1.23E-4", -1.23E-4},
+ {"123.456e-7", 123.456e-7},
+ {"-123.456e-7", -123.456e-7},
+ {"123.456E-7", 123.456E-7},
+ {"-123.456E-7", -123.456E-7},
+ /* try overflowing float */
+ {"2e63", 2e63},
+ {"-2e63", -2e63},
+ {"2E63", 2E63},
+ {"-2E63", -2E63},
+ {"18446744073709551615", (double) UINT64_MAX},
+ {"-9223372036854775808", (double) INT64_MIN},
+ /* try overflowing double */
+ {"2e308", HUGE_VAL},
+ {"-2e308", -HUGE_VAL},
+ {"2E308", HUGE_VAL},
+ {"-2E308", HUGE_VAL},
+};
+
const struct num_unsigned_str num_garbage_positive_strs[] = {
/* valid strings with garbage on the end, should still be valid */
/* decimal */
@@ -183,6 +247,30 @@ const struct num_signed_str num_garbage_negative_strs[] = {
{"-9223372036854775808 garbage", INT64_MIN },
};
+const char *float_invalid_strs[] = {
+ "0.",
+ ".1",
+ "1.1.",
+ "1.1.1",
+ "-0.",
+ "-.1",
+ "-1.1.",
+ "-1.1.1",
+ "e",
+ "1e",
+ "-1e",
+ "0.1e",
+ "-0.1e",
+ "1.e",
+ "-1.e",
+ "1.23e3.4",
+ "-1.23e3.4",
+ "1e1e",
+ "1e1e1",
+ "1e-",
+ "-1e-"
+};
+
const char * num_invalid_strs[] = {
"18446744073709551616", /* out of range unsigned */
"-9223372036854775809", /* out of range negative signed */
@@ -202,7 +290,16 @@ const char * num_invalid_strs[] = {
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
"1111000011110000111100001111000011110000111100001111000011110000"),
+ /* valid float values but should fail to parse as ints */
"1E3",
+ "-1E3",
+ "1.23",
+ "-1.23",
+ "1E-3",
+ "-1E-3",
+ "1.23E4",
+ "-1.23E4",
+ /* misc invalid values */
"0A",
"-B",
"+4",
@@ -216,6 +313,47 @@ const char * num_invalid_strs[] = {
"\0",
};
+static int
+float_cmp(double expected, void *actual_p, enum cmdline_numtype type)
+{
+ double eps;
+ double actual_d;
+
+ if (type == RTE_FLOAT_SINGLE) {
+ /* read as float, convert to double */
+ actual_d = (double)*(float *)actual_p;
+ /* FLT_EPSILON is too small for some tests */
+ eps = 1e-5f;
+ } else {
+ /* read as double */
+ actual_d = *(double *)actual_p;
+ eps = DBL_EPSILON;
+ }
+ /* compare using epsilon value */
+ if (fabs(expected - actual_d) < eps)
+ return 0;
+ /* not equal */
+ return expected < actual_d ? -1 : 1;
+}
+
+static int
+can_parse_float(double expected_result, enum cmdline_numtype type)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ if (expected_result > FLT_MAX || expected_result < -FLT_MAX)
+ return 0;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (expected_result > DBL_MAX || expected_result < -DBL_MAX)
+ return 0;
+ break;
+ default:
+ return 1;
+ }
+ return 1;
+}
+
static int
can_parse_unsigned(uint64_t expected_result, enum cmdline_numtype type)
{
@@ -371,11 +509,11 @@ test_parse_num_invalid_data(void)
int ret = 0;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result; /* pick largest buffer */
cmdline_parse_token_num_t token;
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result; /* pick largest buffer */
token.num_data.type = type;
/* test full strings */
@@ -397,6 +535,31 @@ test_parse_num_invalid_data(void)
}
}
}
+
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result; /* pick largest buffer */
+ token.num_data.type = type;
+
+ /* test full strings */
+ for (i = 0; i < RTE_DIM(float_invalid_strs); i++) {
+
+ memset(&result, 0, sizeof(double));
+ memset(&buf, 0, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*)&token,
+ float_invalid_strs[i], (void*)&result, sizeof(result));
+ if (ret != -1) {
+ /* get some info about what we are trying to parse */
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ printf("Error: parsing %s as %s succeeded!\n",
+ float_invalid_strs[i], buf);
+ return -1;
+ }
+ }
+ }
return 0;
}
@@ -408,13 +571,13 @@ test_parse_num_valid(void)
enum cmdline_numtype type;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result;
cmdline_parse_token_num_t token;
/** valid strings **/
/* cycle through all possible parsed types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive strings */
@@ -489,10 +652,44 @@ test_parse_num_valid(void)
}
}
+ /* float values */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(num_valid_float_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ num_valid_float_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(num_valid_float_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ num_valid_float_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(num_valid_float_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ num_valid_float_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
/** garbage strings **/
/* cycle through all possible parsed types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive garbage strings */
diff --git a/buildtools/dpdk-cmdline-gen.py b/buildtools/dpdk-cmdline-gen.py
index 7dadded783..6c76d7116a 100755
--- a/buildtools/dpdk-cmdline-gen.py
+++ b/buildtools/dpdk-cmdline-gen.py
@@ -17,16 +17,18 @@
RTE_SET_USED(cl);
RTE_SET_USED(data);
"""
-NUMERIC_TYPES = [
- "UINT8",
- "UINT16",
- "UINT32",
- "UINT64",
- "INT8",
- "INT16",
- "INT32",
- "INT64",
-]
+NUMERIC_TYPES = {
+ "UINT8": "uint8_t",
+ "UINT16": "uint16_t",
+ "UINT32": "uint32_t",
+ "UINT64": "uint64_t",
+ "INT8": "int8_t",
+ "INT16": "int16_t",
+ "INT32": "int32_t",
+ "INT64": "int64_t",
+ "FLOAT_SINGLE": "float",
+ "FLOAT_DOUBLE": "double",
+}
def process_command(lineno, tokens, comment):
@@ -70,7 +72,7 @@ def process_command(lineno, tokens, comment):
f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});"
)
elif t_type in NUMERIC_TYPES:
- result_struct.append(f"\t{t_type.lower()}_t {t_name};")
+ result_struct.append(f"\t{NUMERIC_TYPES[t_type]} {t_name};")
initializers.append(
f"static cmdline_parse_token_num_t cmd_{name}_{t_name}_tok =\n"
f"\tTOKEN_NUM_INITIALIZER(struct cmd_{name}_result, {t_name}, RTE_{t_type});"
diff --git a/doc/guides/prog_guide/cmdline.rst b/doc/guides/prog_guide/cmdline.rst
index e20281ceb5..447a90e32e 100644
--- a/doc/guides/prog_guide/cmdline.rst
+++ b/doc/guides/prog_guide/cmdline.rst
@@ -22,6 +22,7 @@ The DPDK command-line library supports the following features:
* Strings
* Signed/unsigned 16/32/64-bit integers
+ * Single/double precision floats
* IP Addresses
* Ethernet Addresses
@@ -68,6 +69,8 @@ The format of the list file must be:
* ``<UINT16>port_id``
+ * ``<FLOAT_SINGLE>ratio``
+
* ``<IP>src_ip``
* ``<IPv4>dst_ip4``
diff --git a/doc/guides/rel_notes/release_25_07.rst b/doc/guides/rel_notes/release_25_07.rst
index 093b85d206..54bc545110 100644
--- a/doc/guides/rel_notes/release_25_07.rst
+++ b/doc/guides/rel_notes/release_25_07.rst
@@ -55,6 +55,11 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added floating point numbers support to cmdline library.**
+
+ The cmdline library now supports parsing single- and double-precision
+ floating point numbers in interactive user commands.
+
Removed Items
-------------
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index f21796bedb..687727b6cc 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -7,6 +7,8 @@
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <string.h>
#include <eal_export.h>
#include <rte_string_fns.h>
@@ -34,6 +36,10 @@ enum num_parse_state_t {
DEC_NEG,
BIN,
HEX,
+ FLOAT_POS,
+ FLOAT_NEG,
+ FLOAT_EXP,
+ FLOAT_NEG_EXP,
ERROR,
@@ -44,12 +50,27 @@ enum num_parse_state_t {
BIN_OK,
DEC_NEG_OK,
DEC_POS_OK,
+ FLOAT_POS_OK,
+ FLOAT_NEG_OK,
+ FLOAT_EXP_POS_OK,
+ FLOAT_EXP_NEG_OK,
+};
+
+struct float_parse_state {
+ uint64_t dec;
+ uint64_t frac;
+ uint64_t frac_exp;
+ uint64_t exp;
+#define FLOAT_FLAG_NEG_RES (1 << 0)
+#define FLOAT_FLAG_NEG_EXP (1 << 1)
+ int flags;
};
/* Keep it sync with enum in .h */
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
"INT8", "INT16", "INT32", "INT64",
+ "SINGLE", "DOUBLE"
};
static inline int
@@ -63,6 +84,50 @@ add_to_res(unsigned int c, uint64_t *res, unsigned int base)
return 0;
}
+static inline int
+check_float_result(enum cmdline_numtype res_type, struct float_parse_state *fps,
+ void *res)
+{
+ double dec, frac, exp, result;
+
+ /* extract parts */
+ dec = (double) fps->dec;
+ frac = (double) fps->frac * pow(10.0, -(double)fps->frac_exp);
+ exp = (double) fps->exp;
+
+ /* exponent might be negative */
+ if (fps->flags & FLOAT_FLAG_NEG_EXP)
+ exp = pow(10.0, -exp);
+ else
+ exp = pow(10.0, exp);
+
+ /* combine decimal, fractional, and exponent parts */
+ result = (dec + frac) * exp;
+
+ /* check for any overflows */
+ if (isinf(frac) || isinf(exp) || isinf(result))
+ return -1;
+
+ /* result is a valid double */
+
+ /* check if result needs to be negative */
+ if (fps->flags & FLOAT_FLAG_NEG_RES)
+ result = -result;
+
+ if (res_type == RTE_FLOAT_SINGLE) {
+ /* float can overflow from conversion */
+ float flt = (float)result;
+ if (isinf(flt))
+ return -1;
+ if (res) *(float *)res = flt;
+ } else if (res_type == RTE_FLOAT_DOUBLE) {
+ if (res) *(double *)res = result;
+ } else {
+ return -1;
+ }
+ return 0;
+}
+
static int
check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
{
@@ -87,6 +152,14 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
if (ressize < sizeof(int64_t))
return -1;
break;
+ case RTE_FLOAT_SINGLE:
+ if (ressize < sizeof(float))
+ return -1;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (ressize < sizeof(double))
+ return -1;
+ break;
default:
return -1;
}
@@ -104,6 +177,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
const char * buf;
char c;
uint64_t res1 = 0;
+ struct float_parse_state fps = {};
if (!tk)
return -1;
@@ -156,6 +230,10 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
else
st = OCTAL_OK;
}
+ else if (c == '.') {
+ st = FLOAT_POS;
+ break;
+ }
else {
st = ERROR;
}
@@ -173,11 +251,80 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
}
break;
+ case FLOAT_POS:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else {
+ st = FLOAT_POS_OK;
+ fps.frac_exp++;
+ }
+ }
+ else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_NEG:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else {
+ st = FLOAT_NEG_OK;
+ fps.frac_exp++;
+ }
+ }
+ else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_EXP:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else
+ st = FLOAT_EXP_POS_OK;
+ }
+ else if (c == '-') {
+ st = FLOAT_NEG_EXP;
+ fps.flags |= FLOAT_FLAG_NEG_EXP;
+ }
+ else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_NEG_EXP:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else
+ st = FLOAT_EXP_NEG_OK;
+ }
+ else {
+ st = ERROR;
+ }
+ break;
+
case DEC_NEG_OK:
if (c >= '0' && c <= '9') {
if (add_to_res(c - '0', &res1, 10) < 0)
st = ERROR;
}
+ else if (c == '.') {
+ fps.dec = res1;
+ fps.flags |= FLOAT_FLAG_NEG_RES;
+ st = FLOAT_NEG;
+ /* erase result */
+ res1 = 0;
+ } else if (c == 'e' || c == 'E') {
+ fps.dec = res1;
+ fps.flags |= FLOAT_FLAG_NEG_RES;
+ st = FLOAT_EXP;
+ /* erase result */
+ res1 = 0;
+ }
else {
st = ERROR;
}
@@ -188,11 +335,75 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
if (add_to_res(c - '0', &res1, 10) < 0)
st = ERROR;
}
+ else if (c == '.') {
+ fps.dec = res1;
+ st = FLOAT_POS;
+ /* erase result */
+ res1 = 0;
+ }
+ else if (c == 'e' || c == 'E') {
+ fps.dec = res1;
+ st = FLOAT_EXP;
+ /* erase result */
+ res1 = 0;
+ }
else {
st = ERROR;
}
break;
+ case FLOAT_POS_OK:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else
+ fps.frac_exp++;
+ } else if (c == 'e' || c == 'E') {
+ fps.frac = res1;
+ st = FLOAT_EXP;
+ /* erase result */
+ res1 = 0;
+ } else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_NEG_OK:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else
+ fps.frac_exp++;
+ } else if (c == 'e' || c == 'E') {
+ fps.frac = res1;
+ st = FLOAT_EXP;
+ /* erase result */
+ res1 = 0;
+ } else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_EXP_POS_OK:
+ /* exponent is always whole */
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ } else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_EXP_NEG_OK:
+ /* exponent is always whole */
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ } else {
+ st = ERROR;
+ }
+ break;
+
case HEX:
st = HEX_OK;
/* fall-through */
@@ -282,6 +493,12 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
} else if (nd.type == RTE_UINT64) {
if (res) *(uint64_t *)res = res1;
return buf-srcbuf;
+ } else if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ /* parsed double from integer */
+ fps.dec = res1;
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
} else {
return -1;
}
@@ -304,6 +521,63 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
res1 <= (uint64_t)INT64_MAX + 1) {
if (res) *(int64_t *)res = (int64_t) (-res1);
return buf-srcbuf;
+ } else if ((nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) &&
+ res1 <= (uint64_t)INT64_MAX + 1) {
+ /* parsed double from negative integer */
+ fps.dec = res1;
+ fps.flags |= FLOAT_FLAG_NEG_RES;
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
+ } else {
+ return -1;
+ }
+ break;
+
+ case FLOAT_POS_OK:
+ if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ fps.frac = res1;
+
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
+ } else {
+ return -1;
+ }
+ break;
+
+ case FLOAT_NEG_OK:
+ if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ fps.frac = res1;
+
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
+ } else {
+ return -1;
+ }
+ break;
+
+ case FLOAT_EXP_POS_OK:
+ /* watch for overflow in the exponent */
+ if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ fps.exp = res1;
+
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
+ } else {
+ return -1;
+ }
+ break;
+
+ case FLOAT_EXP_NEG_OK:
+ if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ fps.exp = res1;
+
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
} else {
return -1;
}
diff --git a/lib/cmdline/cmdline_parse_num.h b/lib/cmdline/cmdline_parse_num.h
index bdd0267612..b2792a2d11 100644
--- a/lib/cmdline/cmdline_parse_num.h
+++ b/lib/cmdline/cmdline_parse_num.h
@@ -22,7 +22,9 @@ enum cmdline_numtype {
RTE_INT8,
RTE_INT16,
RTE_INT32,
- RTE_INT64
+ RTE_INT64,
+ RTE_FLOAT_SINGLE,
+ RTE_FLOAT_DOUBLE,
};
struct cmdline_token_num_data {
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v3 2/2] app/testpmd: add sleep command
2025-05-07 9:50 ` [PATCH v3 " Anatoly Burakov
@ 2025-05-07 9:50 ` Anatoly Burakov
2025-05-07 9:53 ` [PATCH v3 1/2] cmdline: add floating point support Burakov, Anatoly
1 sibling, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-07 9:50 UTC (permalink / raw)
To: dev, Aman Singh
Test-pmd already has a way to run a list of commands from file, but there
is no way to pause execution for a specified amount of time between two
commands. This may be necessary for simple automation, particularly for
waiting on some asynchronous operation such as link status update.
Add a simple sleep command to wait until certain number of seconds has
passed, using the newly added cmdline library floating point support.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v1 -> v2:
- Add floating point support to cmdline
- Use floating point format for pause command
app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index d4bb3ec998..b6152c07e6 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
"quit\n"
" Quit to prompt.\n\n"
+
+ "sleep secs\n"
+ " Sleep for secs seconds (can be fractional).\n\n"
);
}
@@ -7768,6 +7771,37 @@ static cmdline_parse_inst_t cmd_quit = {
},
};
+/* *** SLEEP *** */
+struct cmd_sleep_result {
+ cmdline_fixed_string_t sleep;
+ double secs;
+};
+
+static void cmd_sleep_parsed(void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_sleep_result *res = parsed_result;
+
+ rte_delay_us_sleep(res->secs * 1E6);
+}
+
+static cmdline_parse_token_string_t cmd_sleep_sleep =
+ TOKEN_STRING_INITIALIZER(struct cmd_sleep_result, sleep, "sleep");
+static cmdline_parse_token_num_t cmd_sleep_seconds =
+ TOKEN_NUM_INITIALIZER(struct cmd_sleep_result, secs, RTE_FLOAT_DOUBLE);
+
+static cmdline_parse_inst_t cmd_sleep = {
+ .f = cmd_sleep_parsed,
+ .data = NULL,
+ .help_str = "sleep <secs>: Sleep for a specified number of seconds",
+ .tokens = {
+ (void *)&cmd_sleep_sleep,
+ (void *)&cmd_sleep_seconds,
+ NULL,
+ },
+};
+
/* *** ADD/REMOVE MAC ADDRESS FROM A PORT *** */
struct cmd_mac_addr_result {
cmdline_fixed_string_t mac_addr_cmd;
@@ -13711,6 +13745,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_showdevice,
&cmd_showcfg,
&cmd_showfwdall,
+ &cmd_sleep,
&cmd_start,
&cmd_start_tx_first,
&cmd_start_tx_first_n,
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 1/2] cmdline: add floating point support
2025-05-07 9:50 ` [PATCH v3 " Anatoly Burakov
2025-05-07 9:50 ` [PATCH v3 2/2] app/testpmd: add sleep command Anatoly Burakov
@ 2025-05-07 9:53 ` Burakov, Anatoly
1 sibling, 0 replies; 45+ messages in thread
From: Burakov, Anatoly @ 2025-05-07 9:53 UTC (permalink / raw)
To: dev
On 5/7/2025 11:50 AM, Anatoly Burakov wrote:
> Add support for parsing floating point numbers in cmdline library, as well
> as unit tests for the new functionality. The parser supports single and
> double precision floats, and will understand decimal fractions as well as
> scientific notation.
>
> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> ---
Missed patchnotes:
v2 -> v3:
- Fixed a bug where a free-standing negative exponent ("1e-") would
attempt to be
parsed, and added unit tests for this case
- Added support for floats in dpdk-cmdline-gen script
- Added documentation updates to call out float support
--
Thanks,
Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v4 1/2] cmdline: add floating point support
2025-05-02 12:27 [PATCH v1 1/1] app/testpmd: add sleep command Anatoly Burakov
` (3 preceding siblings ...)
2025-05-07 9:50 ` [PATCH v3 " Anatoly Burakov
@ 2025-05-07 10:01 ` Anatoly Burakov
2025-05-07 10:01 ` [PATCH v4 2/2] app/testpmd: add sleep command Anatoly Burakov
2025-05-07 10:35 ` [PATCH v4 1/2] cmdline: add floating point support Konstantin Ananyev
2025-05-07 15:22 ` [PATCH v5 1/3] cmdline: use C standard library as number parser Anatoly Burakov
` (5 subsequent siblings)
10 siblings, 2 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-07 10:01 UTC (permalink / raw)
To: dev
Add support for parsing floating point numbers in cmdline library, as well
as unit tests for the new functionality. The parser supports single and
double precision floats, and will understand decimal fractions as well as
scientific notation.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v3 -> v4:
- Removed unnecessary check for integer overflow when parsing negative
floats (as we convert to double before changing sign)
- Make naming of float exponent states more consistent
v2 -> v3:
- Fixed a bug where a free-standing negative exponent ("1e-") would attempt to be
parsed, and added unit tests for this case
- Added support for floats in dpdk-cmdline-gen script
- Added documentation updates to call out float support
app/test/test_cmdline_num.c | 203 +++++++++++++++++-
buildtools/dpdk-cmdline-gen.py | 24 ++-
doc/guides/prog_guide/cmdline.rst | 3 +
doc/guides/rel_notes/release_25_07.rst | 5 +
lib/cmdline/cmdline_parse_num.c | 273 +++++++++++++++++++++++++
lib/cmdline/cmdline_parse_num.h | 4 +-
6 files changed, 497 insertions(+), 15 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index 9276de59bd..e4038271c9 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -5,6 +5,8 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <rte_string_fns.h>
@@ -23,6 +25,11 @@ struct num_signed_str {
int64_t result;
};
+struct num_float_str {
+ const char * str;
+ double result;
+};
+
const struct num_unsigned_str num_valid_positive_strs[] = {
/* decimal positive */
{"0", 0 },
@@ -141,6 +148,63 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-9223372036854775808", INT64_MIN },
};
+const struct num_float_str num_valid_float_strs[] = {
+ /* zero */
+ {"0", 0},
+ /* parse int as float */
+ {"1", 1},
+ {"-1", -1},
+ /* fractional */
+ {"1.23", 1.23},
+ {"-1.23", -1.23},
+ {"0.123", 0.123},
+ {"-0.123", -0.123},
+ {"123.456", 123.456},
+ {"-123.456", -123.456},
+ /* positive exponent */
+ {"1e2", 1e2},
+ {"-1e2", -1e2},
+ {"1E2", 1E2},
+ {"-1E2", -1E2},
+ {"0.12e3", 0.12e3},
+ {"-0.12e3", -0.12e3},
+ {"1.23e4", 1.23e4},
+ {"-1.23e4", -1.23e4},
+ {"1.23E4", 1.23E4},
+ {"-1.23E4", -1.23E4},
+ {"123.456e7", 123.456e7},
+ {"-123.456e7", -123.456e7},
+ {"123.456E7", 123.456E7},
+ {"-123.456E7", -123.456E7},
+ /* negative exponent */
+ {"1e-2", 1e-2},
+ {"-1e-2", -1e-2},
+ {"1E-2", 1E-2},
+ {"-1E-2", -1E-2},
+ {"0.12e-3", 0.12e-3},
+ {"-0.12e-3", -0.12e-3},
+ {"1.23e-4", 1.23e-4},
+ {"-1.23e-4", -1.23e-4},
+ {"1.23E-4", 1.23E-4},
+ {"-1.23E-4", -1.23E-4},
+ {"123.456e-7", 123.456e-7},
+ {"-123.456e-7", -123.456e-7},
+ {"123.456E-7", 123.456E-7},
+ {"-123.456E-7", -123.456E-7},
+ /* try overflowing float */
+ {"2e63", 2e63},
+ {"-2e63", -2e63},
+ {"2E63", 2E63},
+ {"-2E63", -2E63},
+ {"18446744073709551615", (double) UINT64_MAX},
+ {"-9223372036854775808", (double) INT64_MIN},
+ /* try overflowing double */
+ {"2e308", HUGE_VAL},
+ {"-2e308", -HUGE_VAL},
+ {"2E308", HUGE_VAL},
+ {"-2E308", HUGE_VAL},
+};
+
const struct num_unsigned_str num_garbage_positive_strs[] = {
/* valid strings with garbage on the end, should still be valid */
/* decimal */
@@ -183,6 +247,30 @@ const struct num_signed_str num_garbage_negative_strs[] = {
{"-9223372036854775808 garbage", INT64_MIN },
};
+const char *float_invalid_strs[] = {
+ "0.",
+ ".1",
+ "1.1.",
+ "1.1.1",
+ "-0.",
+ "-.1",
+ "-1.1.",
+ "-1.1.1",
+ "e",
+ "1e",
+ "-1e",
+ "0.1e",
+ "-0.1e",
+ "1.e",
+ "-1.e",
+ "1.23e3.4",
+ "-1.23e3.4",
+ "1e1e",
+ "1e1e1",
+ "1e-",
+ "-1e-"
+};
+
const char * num_invalid_strs[] = {
"18446744073709551616", /* out of range unsigned */
"-9223372036854775809", /* out of range negative signed */
@@ -202,7 +290,16 @@ const char * num_invalid_strs[] = {
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
"1111000011110000111100001111000011110000111100001111000011110000"),
+ /* valid float values but should fail to parse as ints */
"1E3",
+ "-1E3",
+ "1.23",
+ "-1.23",
+ "1E-3",
+ "-1E-3",
+ "1.23E4",
+ "-1.23E4",
+ /* misc invalid values */
"0A",
"-B",
"+4",
@@ -216,6 +313,47 @@ const char * num_invalid_strs[] = {
"\0",
};
+static int
+float_cmp(double expected, void *actual_p, enum cmdline_numtype type)
+{
+ double eps;
+ double actual_d;
+
+ if (type == RTE_FLOAT_SINGLE) {
+ /* read as float, convert to double */
+ actual_d = (double)*(float *)actual_p;
+ /* FLT_EPSILON is too small for some tests */
+ eps = 1e-5f;
+ } else {
+ /* read as double */
+ actual_d = *(double *)actual_p;
+ eps = DBL_EPSILON;
+ }
+ /* compare using epsilon value */
+ if (fabs(expected - actual_d) < eps)
+ return 0;
+ /* not equal */
+ return expected < actual_d ? -1 : 1;
+}
+
+static int
+can_parse_float(double expected_result, enum cmdline_numtype type)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ if (expected_result > FLT_MAX || expected_result < -FLT_MAX)
+ return 0;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (expected_result > DBL_MAX || expected_result < -DBL_MAX)
+ return 0;
+ break;
+ default:
+ return 1;
+ }
+ return 1;
+}
+
static int
can_parse_unsigned(uint64_t expected_result, enum cmdline_numtype type)
{
@@ -371,11 +509,11 @@ test_parse_num_invalid_data(void)
int ret = 0;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result; /* pick largest buffer */
cmdline_parse_token_num_t token;
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result; /* pick largest buffer */
token.num_data.type = type;
/* test full strings */
@@ -397,6 +535,31 @@ test_parse_num_invalid_data(void)
}
}
}
+
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result; /* pick largest buffer */
+ token.num_data.type = type;
+
+ /* test full strings */
+ for (i = 0; i < RTE_DIM(float_invalid_strs); i++) {
+
+ memset(&result, 0, sizeof(double));
+ memset(&buf, 0, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*)&token,
+ float_invalid_strs[i], (void*)&result, sizeof(result));
+ if (ret != -1) {
+ /* get some info about what we are trying to parse */
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ printf("Error: parsing %s as %s succeeded!\n",
+ float_invalid_strs[i], buf);
+ return -1;
+ }
+ }
+ }
return 0;
}
@@ -408,13 +571,13 @@ test_parse_num_valid(void)
enum cmdline_numtype type;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result;
cmdline_parse_token_num_t token;
/** valid strings **/
/* cycle through all possible parsed types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive strings */
@@ -489,10 +652,44 @@ test_parse_num_valid(void)
}
}
+ /* float values */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(num_valid_float_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ num_valid_float_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(num_valid_float_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ num_valid_float_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(num_valid_float_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ num_valid_float_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
/** garbage strings **/
/* cycle through all possible parsed types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive garbage strings */
diff --git a/buildtools/dpdk-cmdline-gen.py b/buildtools/dpdk-cmdline-gen.py
index 7dadded783..6c76d7116a 100755
--- a/buildtools/dpdk-cmdline-gen.py
+++ b/buildtools/dpdk-cmdline-gen.py
@@ -17,16 +17,18 @@
RTE_SET_USED(cl);
RTE_SET_USED(data);
"""
-NUMERIC_TYPES = [
- "UINT8",
- "UINT16",
- "UINT32",
- "UINT64",
- "INT8",
- "INT16",
- "INT32",
- "INT64",
-]
+NUMERIC_TYPES = {
+ "UINT8": "uint8_t",
+ "UINT16": "uint16_t",
+ "UINT32": "uint32_t",
+ "UINT64": "uint64_t",
+ "INT8": "int8_t",
+ "INT16": "int16_t",
+ "INT32": "int32_t",
+ "INT64": "int64_t",
+ "FLOAT_SINGLE": "float",
+ "FLOAT_DOUBLE": "double",
+}
def process_command(lineno, tokens, comment):
@@ -70,7 +72,7 @@ def process_command(lineno, tokens, comment):
f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});"
)
elif t_type in NUMERIC_TYPES:
- result_struct.append(f"\t{t_type.lower()}_t {t_name};")
+ result_struct.append(f"\t{NUMERIC_TYPES[t_type]} {t_name};")
initializers.append(
f"static cmdline_parse_token_num_t cmd_{name}_{t_name}_tok =\n"
f"\tTOKEN_NUM_INITIALIZER(struct cmd_{name}_result, {t_name}, RTE_{t_type});"
diff --git a/doc/guides/prog_guide/cmdline.rst b/doc/guides/prog_guide/cmdline.rst
index e20281ceb5..447a90e32e 100644
--- a/doc/guides/prog_guide/cmdline.rst
+++ b/doc/guides/prog_guide/cmdline.rst
@@ -22,6 +22,7 @@ The DPDK command-line library supports the following features:
* Strings
* Signed/unsigned 16/32/64-bit integers
+ * Single/double precision floats
* IP Addresses
* Ethernet Addresses
@@ -68,6 +69,8 @@ The format of the list file must be:
* ``<UINT16>port_id``
+ * ``<FLOAT_SINGLE>ratio``
+
* ``<IP>src_ip``
* ``<IPv4>dst_ip4``
diff --git a/doc/guides/rel_notes/release_25_07.rst b/doc/guides/rel_notes/release_25_07.rst
index 093b85d206..54bc545110 100644
--- a/doc/guides/rel_notes/release_25_07.rst
+++ b/doc/guides/rel_notes/release_25_07.rst
@@ -55,6 +55,11 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added floating point numbers support to cmdline library.**
+
+ The cmdline library now supports parsing single- and double-precision
+ floating point numbers in interactive user commands.
+
Removed Items
-------------
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index f21796bedb..9e4d559325 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -7,6 +7,8 @@
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <string.h>
#include <eal_export.h>
#include <rte_string_fns.h>
@@ -34,6 +36,10 @@ enum num_parse_state_t {
DEC_NEG,
BIN,
HEX,
+ FLOAT_POS,
+ FLOAT_NEG,
+ FLOAT_EXP_POS,
+ FLOAT_EXP_NEG,
ERROR,
@@ -44,12 +50,27 @@ enum num_parse_state_t {
BIN_OK,
DEC_NEG_OK,
DEC_POS_OK,
+ FLOAT_POS_OK,
+ FLOAT_NEG_OK,
+ FLOAT_EXP_POS_OK,
+ FLOAT_EXP_NEG_OK,
+};
+
+struct float_parse_state {
+ uint64_t dec;
+ uint64_t frac;
+ uint64_t frac_exp;
+ uint64_t exp;
+#define FLOAT_FLAG_NEG_RES (1 << 0)
+#define FLOAT_FLAG_NEG_EXP (1 << 1)
+ int flags;
};
/* Keep it sync with enum in .h */
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
"INT8", "INT16", "INT32", "INT64",
+ "SINGLE", "DOUBLE"
};
static inline int
@@ -63,6 +84,50 @@ add_to_res(unsigned int c, uint64_t *res, unsigned int base)
return 0;
}
+static inline int
+check_float_result(enum cmdline_numtype res_type, struct float_parse_state *fps,
+ void *res)
+{
+ double dec, frac, exp, result;
+
+ /* extract parts */
+ dec = (double) fps->dec;
+ frac = (double) fps->frac * pow(10.0, -(double)fps->frac_exp);
+ exp = (double) fps->exp;
+
+ /* exponent might be negative */
+ if (fps->flags & FLOAT_FLAG_NEG_EXP)
+ exp = pow(10.0, -exp);
+ else
+ exp = pow(10.0, exp);
+
+ /* combine decimal, fractional, and exponent parts */
+ result = (dec + frac) * exp;
+
+ /* check for any overflows */
+ if (isinf(frac) || isinf(exp) || isinf(result))
+ return -1;
+
+ /* result is a valid double */
+
+ /* check if result needs to be negative */
+ if (fps->flags & FLOAT_FLAG_NEG_RES)
+ result = -result;
+
+ if (res_type == RTE_FLOAT_SINGLE) {
+ /* float can overflow from conversion */
+ float flt = (float)result;
+ if (isinf(flt))
+ return -1;
+ if (res) *(float *)res = flt;
+ } else if (res_type == RTE_FLOAT_DOUBLE) {
+ if (res) *(double *)res = result;
+ } else {
+ return -1;
+ }
+ return 0;
+}
+
static int
check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
{
@@ -87,6 +152,14 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
if (ressize < sizeof(int64_t))
return -1;
break;
+ case RTE_FLOAT_SINGLE:
+ if (ressize < sizeof(float))
+ return -1;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (ressize < sizeof(double))
+ return -1;
+ break;
default:
return -1;
}
@@ -104,6 +177,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
const char * buf;
char c;
uint64_t res1 = 0;
+ struct float_parse_state fps = {};
if (!tk)
return -1;
@@ -156,6 +230,10 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
else
st = OCTAL_OK;
}
+ else if (c == '.') {
+ st = FLOAT_POS;
+ break;
+ }
else {
st = ERROR;
}
@@ -173,11 +251,80 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
}
break;
+ case FLOAT_POS:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else {
+ st = FLOAT_POS_OK;
+ fps.frac_exp++;
+ }
+ }
+ else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_NEG:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else {
+ st = FLOAT_NEG_OK;
+ fps.frac_exp++;
+ }
+ }
+ else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_EXP_POS:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else
+ st = FLOAT_EXP_POS_OK;
+ }
+ else if (c == '-') {
+ st = FLOAT_EXP_NEG;
+ fps.flags |= FLOAT_FLAG_NEG_EXP;
+ }
+ else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_EXP_NEG:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else
+ st = FLOAT_EXP_NEG_OK;
+ }
+ else {
+ st = ERROR;
+ }
+ break;
+
case DEC_NEG_OK:
if (c >= '0' && c <= '9') {
if (add_to_res(c - '0', &res1, 10) < 0)
st = ERROR;
}
+ else if (c == '.') {
+ fps.dec = res1;
+ fps.flags |= FLOAT_FLAG_NEG_RES;
+ st = FLOAT_NEG;
+ /* erase result */
+ res1 = 0;
+ } else if (c == 'e' || c == 'E') {
+ fps.dec = res1;
+ fps.flags |= FLOAT_FLAG_NEG_RES;
+ st = FLOAT_EXP_POS;
+ /* erase result */
+ res1 = 0;
+ }
else {
st = ERROR;
}
@@ -188,11 +335,75 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
if (add_to_res(c - '0', &res1, 10) < 0)
st = ERROR;
}
+ else if (c == '.') {
+ fps.dec = res1;
+ st = FLOAT_POS;
+ /* erase result */
+ res1 = 0;
+ }
+ else if (c == 'e' || c == 'E') {
+ fps.dec = res1;
+ st = FLOAT_EXP_POS;
+ /* erase result */
+ res1 = 0;
+ }
else {
st = ERROR;
}
break;
+ case FLOAT_POS_OK:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else
+ fps.frac_exp++;
+ } else if (c == 'e' || c == 'E') {
+ fps.frac = res1;
+ st = FLOAT_EXP_POS;
+ /* erase result */
+ res1 = 0;
+ } else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_NEG_OK:
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ else
+ fps.frac_exp++;
+ } else if (c == 'e' || c == 'E') {
+ fps.frac = res1;
+ st = FLOAT_EXP_POS;
+ /* erase result */
+ res1 = 0;
+ } else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_EXP_POS_OK:
+ /* exponent is always whole */
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ } else {
+ st = ERROR;
+ }
+ break;
+
+ case FLOAT_EXP_NEG_OK:
+ /* exponent is always whole */
+ if (c >= '0' && c <= '9') {
+ if (add_to_res(c - '0', &res1, 10) < 0)
+ st = ERROR;
+ } else {
+ st = ERROR;
+ }
+ break;
+
case HEX:
st = HEX_OK;
/* fall-through */
@@ -282,6 +493,12 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
} else if (nd.type == RTE_UINT64) {
if (res) *(uint64_t *)res = res1;
return buf-srcbuf;
+ } else if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ /* parsed double from integer */
+ fps.dec = res1;
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
} else {
return -1;
}
@@ -304,6 +521,62 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
res1 <= (uint64_t)INT64_MAX + 1) {
if (res) *(int64_t *)res = (int64_t) (-res1);
return buf-srcbuf;
+ } else if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ /* parsed double from negative integer */
+ fps.dec = res1;
+ fps.flags |= FLOAT_FLAG_NEG_RES;
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
+ } else {
+ return -1;
+ }
+ break;
+
+ case FLOAT_POS_OK:
+ if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ fps.frac = res1;
+
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
+ } else {
+ return -1;
+ }
+ break;
+
+ case FLOAT_NEG_OK:
+ if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ fps.frac = res1;
+
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
+ } else {
+ return -1;
+ }
+ break;
+
+ case FLOAT_EXP_POS_OK:
+ /* watch for overflow in the exponent */
+ if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ fps.exp = res1;
+
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
+ } else {
+ return -1;
+ }
+ break;
+
+ case FLOAT_EXP_NEG_OK:
+ if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
+ fps.exp = res1;
+
+ if (check_float_result(nd.type, &fps, res) < 0)
+ return -1;
+ return buf-srcbuf;
} else {
return -1;
}
diff --git a/lib/cmdline/cmdline_parse_num.h b/lib/cmdline/cmdline_parse_num.h
index bdd0267612..b2792a2d11 100644
--- a/lib/cmdline/cmdline_parse_num.h
+++ b/lib/cmdline/cmdline_parse_num.h
@@ -22,7 +22,9 @@ enum cmdline_numtype {
RTE_INT8,
RTE_INT16,
RTE_INT32,
- RTE_INT64
+ RTE_INT64,
+ RTE_FLOAT_SINGLE,
+ RTE_FLOAT_DOUBLE,
};
struct cmdline_token_num_data {
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v4 2/2] app/testpmd: add sleep command
2025-05-07 10:01 ` [PATCH v4 " Anatoly Burakov
@ 2025-05-07 10:01 ` Anatoly Burakov
2025-05-07 10:35 ` [PATCH v4 1/2] cmdline: add floating point support Konstantin Ananyev
1 sibling, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-07 10:01 UTC (permalink / raw)
To: dev, Aman Singh
Test-pmd already has a way to run a list of commands from file, but there
is no way to pause execution for a specified amount of time between two
commands. This may be necessary for simple automation, particularly for
waiting on some asynchronous operation such as link status update.
Add a simple sleep command to wait until certain number of seconds has
passed, using the newly added cmdline library floating point support.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v1 -> v2:
- Add floating point support to cmdline
- Use floating point format for pause command
app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index d4bb3ec998..b6152c07e6 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
"quit\n"
" Quit to prompt.\n\n"
+
+ "sleep secs\n"
+ " Sleep for secs seconds (can be fractional).\n\n"
);
}
@@ -7768,6 +7771,37 @@ static cmdline_parse_inst_t cmd_quit = {
},
};
+/* *** SLEEP *** */
+struct cmd_sleep_result {
+ cmdline_fixed_string_t sleep;
+ double secs;
+};
+
+static void cmd_sleep_parsed(void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_sleep_result *res = parsed_result;
+
+ rte_delay_us_sleep(res->secs * 1E6);
+}
+
+static cmdline_parse_token_string_t cmd_sleep_sleep =
+ TOKEN_STRING_INITIALIZER(struct cmd_sleep_result, sleep, "sleep");
+static cmdline_parse_token_num_t cmd_sleep_seconds =
+ TOKEN_NUM_INITIALIZER(struct cmd_sleep_result, secs, RTE_FLOAT_DOUBLE);
+
+static cmdline_parse_inst_t cmd_sleep = {
+ .f = cmd_sleep_parsed,
+ .data = NULL,
+ .help_str = "sleep <secs>: Sleep for a specified number of seconds",
+ .tokens = {
+ (void *)&cmd_sleep_sleep,
+ (void *)&cmd_sleep_seconds,
+ NULL,
+ },
+};
+
/* *** ADD/REMOVE MAC ADDRESS FROM A PORT *** */
struct cmd_mac_addr_result {
cmdline_fixed_string_t mac_addr_cmd;
@@ -13711,6 +13745,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_showdevice,
&cmd_showcfg,
&cmd_showfwdall,
+ &cmd_sleep,
&cmd_start,
&cmd_start_tx_first,
&cmd_start_tx_first_n,
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* RE: [PATCH v4 1/2] cmdline: add floating point support
2025-05-07 10:01 ` [PATCH v4 " Anatoly Burakov
2025-05-07 10:01 ` [PATCH v4 2/2] app/testpmd: add sleep command Anatoly Burakov
@ 2025-05-07 10:35 ` Konstantin Ananyev
2025-05-07 11:06 ` Burakov, Anatoly
1 sibling, 1 reply; 45+ messages in thread
From: Konstantin Ananyev @ 2025-05-07 10:35 UTC (permalink / raw)
To: Anatoly Burakov, dev
> Add support for parsing floating point numbers in cmdline library, as well
> as unit tests for the new functionality. The parser supports single and
> double precision floats, and will understand decimal fractions as well as
> scientific notation.
There are standard functions for that: strtod/strtof - can't we simply use them?
>
> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> ---
>
> Notes:
> v3 -> v4:
> - Removed unnecessary check for integer overflow when parsing negative
> floats (as we convert to double before changing sign)
> - Make naming of float exponent states more consistent
>
> v2 -> v3:
> - Fixed a bug where a free-standing negative exponent ("1e-") would attempt to be
> parsed, and added unit tests for this case
> - Added support for floats in dpdk-cmdline-gen script
> - Added documentation updates to call out float support
>
> app/test/test_cmdline_num.c | 203 +++++++++++++++++-
> buildtools/dpdk-cmdline-gen.py | 24 ++-
> doc/guides/prog_guide/cmdline.rst | 3 +
> doc/guides/rel_notes/release_25_07.rst | 5 +
> lib/cmdline/cmdline_parse_num.c | 273 +++++++++++++++++++++++++
> lib/cmdline/cmdline_parse_num.h | 4 +-
> 6 files changed, 497 insertions(+), 15 deletions(-)
>
> diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
> index 9276de59bd..e4038271c9 100644
> --- a/app/test/test_cmdline_num.c
> +++ b/app/test/test_cmdline_num.c
> @@ -5,6 +5,8 @@
> #include <stdio.h>
> #include <string.h>
> #include <inttypes.h>
> +#include <float.h>
> +#include <math.h>
>
> #include <rte_string_fns.h>
>
> @@ -23,6 +25,11 @@ struct num_signed_str {
> int64_t result;
> };
>
> +struct num_float_str {
> + const char * str;
> + double result;
> +};
> +
> const struct num_unsigned_str num_valid_positive_strs[] = {
> /* decimal positive */
> {"0", 0 },
> @@ -141,6 +148,63 @@ const struct num_signed_str num_valid_negative_strs[] = {
> {"-9223372036854775808", INT64_MIN },
> };
>
> +const struct num_float_str num_valid_float_strs[] = {
> + /* zero */
> + {"0", 0},
> + /* parse int as float */
> + {"1", 1},
> + {"-1", -1},
> + /* fractional */
> + {"1.23", 1.23},
> + {"-1.23", -1.23},
> + {"0.123", 0.123},
> + {"-0.123", -0.123},
> + {"123.456", 123.456},
> + {"-123.456", -123.456},
> + /* positive exponent */
> + {"1e2", 1e2},
> + {"-1e2", -1e2},
> + {"1E2", 1E2},
> + {"-1E2", -1E2},
> + {"0.12e3", 0.12e3},
> + {"-0.12e3", -0.12e3},
> + {"1.23e4", 1.23e4},
> + {"-1.23e4", -1.23e4},
> + {"1.23E4", 1.23E4},
> + {"-1.23E4", -1.23E4},
> + {"123.456e7", 123.456e7},
> + {"-123.456e7", -123.456e7},
> + {"123.456E7", 123.456E7},
> + {"-123.456E7", -123.456E7},
> + /* negative exponent */
> + {"1e-2", 1e-2},
> + {"-1e-2", -1e-2},
> + {"1E-2", 1E-2},
> + {"-1E-2", -1E-2},
> + {"0.12e-3", 0.12e-3},
> + {"-0.12e-3", -0.12e-3},
> + {"1.23e-4", 1.23e-4},
> + {"-1.23e-4", -1.23e-4},
> + {"1.23E-4", 1.23E-4},
> + {"-1.23E-4", -1.23E-4},
> + {"123.456e-7", 123.456e-7},
> + {"-123.456e-7", -123.456e-7},
> + {"123.456E-7", 123.456E-7},
> + {"-123.456E-7", -123.456E-7},
> + /* try overflowing float */
> + {"2e63", 2e63},
> + {"-2e63", -2e63},
> + {"2E63", 2E63},
> + {"-2E63", -2E63},
> + {"18446744073709551615", (double) UINT64_MAX},
> + {"-9223372036854775808", (double) INT64_MIN},
> + /* try overflowing double */
> + {"2e308", HUGE_VAL},
> + {"-2e308", -HUGE_VAL},
> + {"2E308", HUGE_VAL},
> + {"-2E308", HUGE_VAL},
> +};
> +
> const struct num_unsigned_str num_garbage_positive_strs[] = {
> /* valid strings with garbage on the end, should still be valid */
> /* decimal */
> @@ -183,6 +247,30 @@ const struct num_signed_str num_garbage_negative_strs[] = {
> {"-9223372036854775808 garbage", INT64_MIN },
> };
>
> +const char *float_invalid_strs[] = {
> + "0.",
> + ".1",
> + "1.1.",
> + "1.1.1",
> + "-0.",
> + "-.1",
> + "-1.1.",
> + "-1.1.1",
> + "e",
> + "1e",
> + "-1e",
> + "0.1e",
> + "-0.1e",
> + "1.e",
> + "-1.e",
> + "1.23e3.4",
> + "-1.23e3.4",
> + "1e1e",
> + "1e1e1",
> + "1e-",
> + "-1e-"
> +};
> +
> const char * num_invalid_strs[] = {
> "18446744073709551616", /* out of range unsigned */
> "-9223372036854775809", /* out of range negative signed */
> @@ -202,7 +290,16 @@ const char * num_invalid_strs[] = {
> /* too long (128+ chars) */
> ("0b1111000011110000111100001111000011110000111100001111000011110000"
> "1111000011110000111100001111000011110000111100001111000011110000"),
> + /* valid float values but should fail to parse as ints */
> "1E3",
> + "-1E3",
> + "1.23",
> + "-1.23",
> + "1E-3",
> + "-1E-3",
> + "1.23E4",
> + "-1.23E4",
> + /* misc invalid values */
> "0A",
> "-B",
> "+4",
> @@ -216,6 +313,47 @@ const char * num_invalid_strs[] = {
> "\0",
> };
>
> +static int
> +float_cmp(double expected, void *actual_p, enum cmdline_numtype type)
> +{
> + double eps;
> + double actual_d;
> +
> + if (type == RTE_FLOAT_SINGLE) {
> + /* read as float, convert to double */
> + actual_d = (double)*(float *)actual_p;
> + /* FLT_EPSILON is too small for some tests */
> + eps = 1e-5f;
> + } else {
> + /* read as double */
> + actual_d = *(double *)actual_p;
> + eps = DBL_EPSILON;
> + }
> + /* compare using epsilon value */
> + if (fabs(expected - actual_d) < eps)
> + return 0;
> + /* not equal */
> + return expected < actual_d ? -1 : 1;
> +}
> +
> +static int
> +can_parse_float(double expected_result, enum cmdline_numtype type)
> +{
> + switch (type) {
> + case RTE_FLOAT_SINGLE:
> + if (expected_result > FLT_MAX || expected_result < -FLT_MAX)
> + return 0;
> + break;
> + case RTE_FLOAT_DOUBLE:
> + if (expected_result > DBL_MAX || expected_result < -DBL_MAX)
> + return 0;
> + break;
> + default:
> + return 1;
> + }
> + return 1;
> +}
> +
> static int
> can_parse_unsigned(uint64_t expected_result, enum cmdline_numtype type)
> {
> @@ -371,11 +509,11 @@ test_parse_num_invalid_data(void)
> int ret = 0;
> unsigned i;
> char buf[CMDLINE_TEST_BUFSIZE];
> - uint64_t result; /* pick largest buffer */
> cmdline_parse_token_num_t token;
>
> - /* cycle through all possible parsed types */
> + /* cycle through all possible integer types */
> for (type = RTE_UINT8; type <= RTE_INT64; type++) {
> + uint64_t result; /* pick largest buffer */
> token.num_data.type = type;
>
> /* test full strings */
> @@ -397,6 +535,31 @@ test_parse_num_invalid_data(void)
> }
> }
> }
> +
> + /* cycle through all possible float types */
> + for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
> + double result; /* pick largest buffer */
> + token.num_data.type = type;
> +
> + /* test full strings */
> + for (i = 0; i < RTE_DIM(float_invalid_strs); i++) {
> +
> + memset(&result, 0, sizeof(double));
> + memset(&buf, 0, sizeof(buf));
> +
> + ret = cmdline_parse_num((cmdline_parse_token_hdr_t*)&token,
> + float_invalid_strs[i], (void*)&result, sizeof(result));
> + if (ret != -1) {
> + /* get some info about what we are trying to parse */
> + cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
> + buf, sizeof(buf));
> +
> + printf("Error: parsing %s as %s succeeded!\n",
> + float_invalid_strs[i], buf);
> + return -1;
> + }
> + }
> + }
> return 0;
> }
>
> @@ -408,13 +571,13 @@ test_parse_num_valid(void)
> enum cmdline_numtype type;
> unsigned i;
> char buf[CMDLINE_TEST_BUFSIZE];
> - uint64_t result;
> cmdline_parse_token_num_t token;
>
> /** valid strings **/
>
> /* cycle through all possible parsed types */
> for (type = RTE_UINT8; type <= RTE_INT64; type++) {
> + uint64_t result;
> token.num_data.type = type;
>
> /* test positive strings */
> @@ -489,10 +652,44 @@ test_parse_num_valid(void)
> }
> }
>
> + /* float values */
> + for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
> + double result;
> + token.num_data.type = type;
> +
> + /* test all valid strings */
> + for (i = 0; i < RTE_DIM(num_valid_float_strs); i++) {
> + result = 0;
> + memset(&buf, 0, sizeof(buf));
> +
> +
> + cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
> + buf, sizeof(buf));
> +
> + ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
> + num_valid_float_strs[i].str,
> + (void*)&result, sizeof(result));
> +
> + /* if it should have passed but didn't, or if it should have failed but didn't */
> + if ((ret < 0) == (can_parse_float(num_valid_float_strs[i].result, type) > 0)) {
> + printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
> + num_valid_float_strs[i].str, buf);
> + return -1;
> + }
> + /* check if result matches */
> + if (ret > 0 && float_cmp(num_valid_float_strs[i].result, &result, type) != 0) {
> + printf("Error: parsing %s as %s failed: result mismatch!\n",
> + num_valid_float_strs[i].str, buf);
> + return -1;
> + }
> + }
> + }
> +
> /** garbage strings **/
>
> /* cycle through all possible parsed types */
> for (type = RTE_UINT8; type <= RTE_INT64; type++) {
> + uint64_t result;
> token.num_data.type = type;
>
> /* test positive garbage strings */
> diff --git a/buildtools/dpdk-cmdline-gen.py b/buildtools/dpdk-cmdline-gen.py
> index 7dadded783..6c76d7116a 100755
> --- a/buildtools/dpdk-cmdline-gen.py
> +++ b/buildtools/dpdk-cmdline-gen.py
> @@ -17,16 +17,18 @@
> RTE_SET_USED(cl);
> RTE_SET_USED(data);
> """
> -NUMERIC_TYPES = [
> - "UINT8",
> - "UINT16",
> - "UINT32",
> - "UINT64",
> - "INT8",
> - "INT16",
> - "INT32",
> - "INT64",
> -]
> +NUMERIC_TYPES = {
> + "UINT8": "uint8_t",
> + "UINT16": "uint16_t",
> + "UINT32": "uint32_t",
> + "UINT64": "uint64_t",
> + "INT8": "int8_t",
> + "INT16": "int16_t",
> + "INT32": "int32_t",
> + "INT64": "int64_t",
> + "FLOAT_SINGLE": "float",
> + "FLOAT_DOUBLE": "double",
> +}
>
>
> def process_command(lineno, tokens, comment):
> @@ -70,7 +72,7 @@ def process_command(lineno, tokens, comment):
> f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});"
> )
> elif t_type in NUMERIC_TYPES:
> - result_struct.append(f"\t{t_type.lower()}_t {t_name};")
> + result_struct.append(f"\t{NUMERIC_TYPES[t_type]} {t_name};")
> initializers.append(
> f"static cmdline_parse_token_num_t cmd_{name}_{t_name}_tok =\n"
> f"\tTOKEN_NUM_INITIALIZER(struct cmd_{name}_result, {t_name}, RTE_{t_type});"
> diff --git a/doc/guides/prog_guide/cmdline.rst b/doc/guides/prog_guide/cmdline.rst
> index e20281ceb5..447a90e32e 100644
> --- a/doc/guides/prog_guide/cmdline.rst
> +++ b/doc/guides/prog_guide/cmdline.rst
> @@ -22,6 +22,7 @@ The DPDK command-line library supports the following features:
>
> * Strings
> * Signed/unsigned 16/32/64-bit integers
> + * Single/double precision floats
> * IP Addresses
> * Ethernet Addresses
>
> @@ -68,6 +69,8 @@ The format of the list file must be:
>
> * ``<UINT16>port_id``
>
> + * ``<FLOAT_SINGLE>ratio``
> +
> * ``<IP>src_ip``
>
> * ``<IPv4>dst_ip4``
> diff --git a/doc/guides/rel_notes/release_25_07.rst b/doc/guides/rel_notes/release_25_07.rst
> index 093b85d206..54bc545110 100644
> --- a/doc/guides/rel_notes/release_25_07.rst
> +++ b/doc/guides/rel_notes/release_25_07.rst
> @@ -55,6 +55,11 @@ New Features
> Also, make sure to start the actual text at the margin.
> =======================================================
>
> +* **Added floating point numbers support to cmdline library.**
> +
> + The cmdline library now supports parsing single- and double-precision
> + floating point numbers in interactive user commands.
> +
>
> Removed Items
> -------------
> diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
> index f21796bedb..9e4d559325 100644
> --- a/lib/cmdline/cmdline_parse_num.c
> +++ b/lib/cmdline/cmdline_parse_num.c
> @@ -7,6 +7,8 @@
> #include <stdio.h>
> #include <stdint.h>
> #include <inttypes.h>
> +#include <float.h>
> +#include <math.h>
> #include <string.h>
> #include <eal_export.h>
> #include <rte_string_fns.h>
> @@ -34,6 +36,10 @@ enum num_parse_state_t {
> DEC_NEG,
> BIN,
> HEX,
> + FLOAT_POS,
> + FLOAT_NEG,
> + FLOAT_EXP_POS,
> + FLOAT_EXP_NEG,
>
> ERROR,
>
> @@ -44,12 +50,27 @@ enum num_parse_state_t {
> BIN_OK,
> DEC_NEG_OK,
> DEC_POS_OK,
> + FLOAT_POS_OK,
> + FLOAT_NEG_OK,
> + FLOAT_EXP_POS_OK,
> + FLOAT_EXP_NEG_OK,
> +};
> +
> +struct float_parse_state {
> + uint64_t dec;
> + uint64_t frac;
> + uint64_t frac_exp;
> + uint64_t exp;
> +#define FLOAT_FLAG_NEG_RES (1 << 0)
> +#define FLOAT_FLAG_NEG_EXP (1 << 1)
> + int flags;
> };
>
> /* Keep it sync with enum in .h */
> static const char * num_help[] = {
> "UINT8", "UINT16", "UINT32", "UINT64",
> "INT8", "INT16", "INT32", "INT64",
> + "SINGLE", "DOUBLE"
> };
>
> static inline int
> @@ -63,6 +84,50 @@ add_to_res(unsigned int c, uint64_t *res, unsigned int base)
> return 0;
> }
>
> +static inline int
> +check_float_result(enum cmdline_numtype res_type, struct float_parse_state *fps,
> + void *res)
> +{
> + double dec, frac, exp, result;
> +
> + /* extract parts */
> + dec = (double) fps->dec;
> + frac = (double) fps->frac * pow(10.0, -(double)fps->frac_exp);
> + exp = (double) fps->exp;
> +
> + /* exponent might be negative */
> + if (fps->flags & FLOAT_FLAG_NEG_EXP)
> + exp = pow(10.0, -exp);
> + else
> + exp = pow(10.0, exp);
> +
> + /* combine decimal, fractional, and exponent parts */
> + result = (dec + frac) * exp;
> +
> + /* check for any overflows */
> + if (isinf(frac) || isinf(exp) || isinf(result))
> + return -1;
> +
> + /* result is a valid double */
> +
> + /* check if result needs to be negative */
> + if (fps->flags & FLOAT_FLAG_NEG_RES)
> + result = -result;
> +
> + if (res_type == RTE_FLOAT_SINGLE) {
> + /* float can overflow from conversion */
> + float flt = (float)result;
> + if (isinf(flt))
> + return -1;
> + if (res) *(float *)res = flt;
> + } else if (res_type == RTE_FLOAT_DOUBLE) {
> + if (res) *(double *)res = result;
> + } else {
> + return -1;
> + }
> + return 0;
> +}
> +
> static int
> check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
> {
> @@ -87,6 +152,14 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
> if (ressize < sizeof(int64_t))
> return -1;
> break;
> + case RTE_FLOAT_SINGLE:
> + if (ressize < sizeof(float))
> + return -1;
> + break;
> + case RTE_FLOAT_DOUBLE:
> + if (ressize < sizeof(double))
> + return -1;
> + break;
> default:
> return -1;
> }
> @@ -104,6 +177,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
> const char * buf;
> char c;
> uint64_t res1 = 0;
> + struct float_parse_state fps = {};
>
> if (!tk)
> return -1;
> @@ -156,6 +230,10 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
> else
> st = OCTAL_OK;
> }
> + else if (c == '.') {
> + st = FLOAT_POS;
> + break;
> + }
> else {
> st = ERROR;
> }
> @@ -173,11 +251,80 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
> }
> break;
>
> + case FLOAT_POS:
> + if (c >= '0' && c <= '9') {
> + if (add_to_res(c - '0', &res1, 10) < 0)
> + st = ERROR;
> + else {
> + st = FLOAT_POS_OK;
> + fps.frac_exp++;
> + }
> + }
> + else {
> + st = ERROR;
> + }
> + break;
> +
> + case FLOAT_NEG:
> + if (c >= '0' && c <= '9') {
> + if (add_to_res(c - '0', &res1, 10) < 0)
> + st = ERROR;
> + else {
> + st = FLOAT_NEG_OK;
> + fps.frac_exp++;
> + }
> + }
> + else {
> + st = ERROR;
> + }
> + break;
> +
> + case FLOAT_EXP_POS:
> + if (c >= '0' && c <= '9') {
> + if (add_to_res(c - '0', &res1, 10) < 0)
> + st = ERROR;
> + else
> + st = FLOAT_EXP_POS_OK;
> + }
> + else if (c == '-') {
> + st = FLOAT_EXP_NEG;
> + fps.flags |= FLOAT_FLAG_NEG_EXP;
> + }
> + else {
> + st = ERROR;
> + }
> + break;
> +
> + case FLOAT_EXP_NEG:
> + if (c >= '0' && c <= '9') {
> + if (add_to_res(c - '0', &res1, 10) < 0)
> + st = ERROR;
> + else
> + st = FLOAT_EXP_NEG_OK;
> + }
> + else {
> + st = ERROR;
> + }
> + break;
> +
> case DEC_NEG_OK:
> if (c >= '0' && c <= '9') {
> if (add_to_res(c - '0', &res1, 10) < 0)
> st = ERROR;
> }
> + else if (c == '.') {
> + fps.dec = res1;
> + fps.flags |= FLOAT_FLAG_NEG_RES;
> + st = FLOAT_NEG;
> + /* erase result */
> + res1 = 0;
> + } else if (c == 'e' || c == 'E') {
> + fps.dec = res1;
> + fps.flags |= FLOAT_FLAG_NEG_RES;
> + st = FLOAT_EXP_POS;
> + /* erase result */
> + res1 = 0;
> + }
> else {
> st = ERROR;
> }
> @@ -188,11 +335,75 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
> if (add_to_res(c - '0', &res1, 10) < 0)
> st = ERROR;
> }
> + else if (c == '.') {
> + fps.dec = res1;
> + st = FLOAT_POS;
> + /* erase result */
> + res1 = 0;
> + }
> + else if (c == 'e' || c == 'E') {
> + fps.dec = res1;
> + st = FLOAT_EXP_POS;
> + /* erase result */
> + res1 = 0;
> + }
> else {
> st = ERROR;
> }
> break;
>
> + case FLOAT_POS_OK:
> + if (c >= '0' && c <= '9') {
> + if (add_to_res(c - '0', &res1, 10) < 0)
> + st = ERROR;
> + else
> + fps.frac_exp++;
> + } else if (c == 'e' || c == 'E') {
> + fps.frac = res1;
> + st = FLOAT_EXP_POS;
> + /* erase result */
> + res1 = 0;
> + } else {
> + st = ERROR;
> + }
> + break;
> +
> + case FLOAT_NEG_OK:
> + if (c >= '0' && c <= '9') {
> + if (add_to_res(c - '0', &res1, 10) < 0)
> + st = ERROR;
> + else
> + fps.frac_exp++;
> + } else if (c == 'e' || c == 'E') {
> + fps.frac = res1;
> + st = FLOAT_EXP_POS;
> + /* erase result */
> + res1 = 0;
> + } else {
> + st = ERROR;
> + }
> + break;
> +
> + case FLOAT_EXP_POS_OK:
> + /* exponent is always whole */
> + if (c >= '0' && c <= '9') {
> + if (add_to_res(c - '0', &res1, 10) < 0)
> + st = ERROR;
> + } else {
> + st = ERROR;
> + }
> + break;
> +
> + case FLOAT_EXP_NEG_OK:
> + /* exponent is always whole */
> + if (c >= '0' && c <= '9') {
> + if (add_to_res(c - '0', &res1, 10) < 0)
> + st = ERROR;
> + } else {
> + st = ERROR;
> + }
> + break;
> +
> case HEX:
> st = HEX_OK;
> /* fall-through */
> @@ -282,6 +493,12 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
> } else if (nd.type == RTE_UINT64) {
> if (res) *(uint64_t *)res = res1;
> return buf-srcbuf;
> + } else if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
> + /* parsed double from integer */
> + fps.dec = res1;
> + if (check_float_result(nd.type, &fps, res) < 0)
> + return -1;
> + return buf-srcbuf;
> } else {
> return -1;
> }
> @@ -304,6 +521,62 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
> res1 <= (uint64_t)INT64_MAX + 1) {
> if (res) *(int64_t *)res = (int64_t) (-res1);
> return buf-srcbuf;
> + } else if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
> + /* parsed double from negative integer */
> + fps.dec = res1;
> + fps.flags |= FLOAT_FLAG_NEG_RES;
> + if (check_float_result(nd.type, &fps, res) < 0)
> + return -1;
> + return buf-srcbuf;
> + } else {
> + return -1;
> + }
> + break;
> +
> + case FLOAT_POS_OK:
> + if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
> + fps.frac = res1;
> +
> + if (check_float_result(nd.type, &fps, res) < 0)
> + return -1;
> + return buf-srcbuf;
> + } else {
> + return -1;
> + }
> + break;
> +
> + case FLOAT_NEG_OK:
> + if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
> + fps.frac = res1;
> +
> + if (check_float_result(nd.type, &fps, res) < 0)
> + return -1;
> + return buf-srcbuf;
> + } else {
> + return -1;
> + }
> + break;
> +
> + case FLOAT_EXP_POS_OK:
> + /* watch for overflow in the exponent */
> + if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
> + fps.exp = res1;
> +
> + if (check_float_result(nd.type, &fps, res) < 0)
> + return -1;
> + return buf-srcbuf;
> + } else {
> + return -1;
> + }
> + break;
> +
> + case FLOAT_EXP_NEG_OK:
> + if (nd.type == RTE_FLOAT_SINGLE || nd.type == RTE_FLOAT_DOUBLE) {
> + fps.exp = res1;
> +
> + if (check_float_result(nd.type, &fps, res) < 0)
> + return -1;
> + return buf-srcbuf;
> } else {
> return -1;
> }
> diff --git a/lib/cmdline/cmdline_parse_num.h b/lib/cmdline/cmdline_parse_num.h
> index bdd0267612..b2792a2d11 100644
> --- a/lib/cmdline/cmdline_parse_num.h
> +++ b/lib/cmdline/cmdline_parse_num.h
> @@ -22,7 +22,9 @@ enum cmdline_numtype {
> RTE_INT8,
> RTE_INT16,
> RTE_INT32,
> - RTE_INT64
> + RTE_INT64,
> + RTE_FLOAT_SINGLE,
> + RTE_FLOAT_DOUBLE,
> };
>
> struct cmdline_token_num_data {
> --
> 2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 1/2] cmdline: add floating point support
2025-05-07 10:35 ` [PATCH v4 1/2] cmdline: add floating point support Konstantin Ananyev
@ 2025-05-07 11:06 ` Burakov, Anatoly
2025-05-07 12:24 ` Konstantin Ananyev
0 siblings, 1 reply; 45+ messages in thread
From: Burakov, Anatoly @ 2025-05-07 11:06 UTC (permalink / raw)
To: Konstantin Ananyev, dev
On 5/7/2025 12:35 PM, Konstantin Ananyev wrote:
>
>
>> Add support for parsing floating point numbers in cmdline library, as well
>> as unit tests for the new functionality. The parser supports single and
>> double precision floats, and will understand decimal fractions as well as
>> scientific notation.
>
> There are standard functions for that: strtod/strtof - can't we simply use them?
I can ask the same question of the entire existence of this part of the
library: there are strtoull-type functions that should be available to
all targets, so if we're going to use them for floating point parsing,
we might as well remove the number parsing part of the library entirely.
Either way is fine by me.
--
Thanks,
Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* RE: [PATCH v4 1/2] cmdline: add floating point support
2025-05-07 11:06 ` Burakov, Anatoly
@ 2025-05-07 12:24 ` Konstantin Ananyev
2025-05-07 14:06 ` Burakov, Anatoly
0 siblings, 1 reply; 45+ messages in thread
From: Konstantin Ananyev @ 2025-05-07 12:24 UTC (permalink / raw)
To: Burakov, Anatoly, dev
> -----Original Message-----
> From: Burakov, Anatoly <anatoly.burakov@intel.com>
> Sent: Wednesday, May 7, 2025 12:07 PM
> To: Konstantin Ananyev <konstantin.ananyev@huawei.com>; dev@dpdk.org
> Subject: Re: [PATCH v4 1/2] cmdline: add floating point support
>
> On 5/7/2025 12:35 PM, Konstantin Ananyev wrote:
> >
> >
> >> Add support for parsing floating point numbers in cmdline library, as well
> >> as unit tests for the new functionality. The parser supports single and
> >> double precision floats, and will understand decimal fractions as well as
> >> scientific notation.
> >
> > There are standard functions for that: strtod/strtof - can't we simply use them?
>
> I can ask the same question of the entire existence of this part of the
> library: there are strtoull-type functions that should be available to
> all targets,
Probably due to historical reasons - a while ago DPDK was able to run on bare-metal (not any more).
> so if we're going to use them for floating point parsing,
> we might as well remove the number parsing part of the library entirely.
Sounds like a good cleanup for me.
+1 for it.
> Either way is fine by me.
>
> --
> Thanks,
> Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 1/2] cmdline: add floating point support
2025-05-07 12:24 ` Konstantin Ananyev
@ 2025-05-07 14:06 ` Burakov, Anatoly
0 siblings, 0 replies; 45+ messages in thread
From: Burakov, Anatoly @ 2025-05-07 14:06 UTC (permalink / raw)
To: Konstantin Ananyev, dev
On 5/7/2025 2:24 PM, Konstantin Ananyev wrote:
>
>
>> -----Original Message-----
>> From: Burakov, Anatoly <anatoly.burakov@intel.com>
>> Sent: Wednesday, May 7, 2025 12:07 PM
>> To: Konstantin Ananyev <konstantin.ananyev@huawei.com>; dev@dpdk.org
>> Subject: Re: [PATCH v4 1/2] cmdline: add floating point support
>>
>> On 5/7/2025 12:35 PM, Konstantin Ananyev wrote:
>>>
>>>
>>>> Add support for parsing floating point numbers in cmdline library, as well
>>>> as unit tests for the new functionality. The parser supports single and
>>>> double precision floats, and will understand decimal fractions as well as
>>>> scientific notation.
>>>
>>> There are standard functions for that: strtod/strtof - can't we simply use them?
>>
>> I can ask the same question of the entire existence of this part of the
>> library: there are strtoull-type functions that should be available to
>> all targets,
>
> Probably due to historical reasons - a while ago DPDK was able to run on bare-metal (not any more).
>
>> so if we're going to use them for floating point parsing,
>> we might as well remove the number parsing part of the library entirely.
>
> Sounds like a good cleanup for me.
> +1 for it.
>
>
>> Either way is fine by me.
>>
>> --
>> Thanks,
>> Anatoly
>
There's a small number of differences between what DPDK can do and what
strtoull/strtod can do. For example strtoull doesn't support binary
while we do. Strtoull has a concept of "negative hex numbers" while we
consider them to be invalid. Still, seems to work now so I'll submit a
v5 with the rework.
--
Thanks,
Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v5 1/3] cmdline: use C standard library as number parser
2025-05-02 12:27 [PATCH v1 1/1] app/testpmd: add sleep command Anatoly Burakov
` (4 preceding siblings ...)
2025-05-07 10:01 ` [PATCH v4 " Anatoly Burakov
@ 2025-05-07 15:22 ` Anatoly Burakov
2025-05-07 15:22 ` [PATCH v5 2/3] cmdline: add floating point support Anatoly Burakov
` (2 more replies)
2025-05-08 9:53 ` [PATCH v6 " Anatoly Burakov
` (4 subsequent siblings)
10 siblings, 3 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-07 15:22 UTC (permalink / raw)
To: dev
Remove custom number parser and use C standard library instead. In order to
keep compatibility with earlier versions of the parser, we have to take
into account a few quirks:
- We do not consider "negative" numbers to be valid for anything other than
base-10 numbers, whereas C standard library does. We work around that by
forcing base-10 when parsing numbers we expect to be negative.
- We do not consider numbers such as "+4" to be valid, whereas C standard
library does. No one probably relies on this, so we just remove it from
our tests, as it is now a valid number.
- C standard library's strtoull does not do range checks on negative
numbers, so we have to parse knowingly-negative numbers as signed. We've
already changed this to be the case on account of point 1 from this list,
so no biggie.
- C standard library does not support binary numbers, so we keep around the
relevant parts of the custom parser in place to support binary numbers.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
app/test/test_cmdline_num.c | 1 -
lib/cmdline/cmdline_parse_num.c | 353 +++++++++++++++-----------------
2 files changed, 167 insertions(+), 187 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index 9276de59bd..471e9964ee 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -205,7 +205,6 @@ const char * num_invalid_strs[] = {
"1E3",
"0A",
"-B",
- "+4",
"1.23G",
"",
" ",
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index f21796bedb..f7ccd26d96 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -4,6 +4,7 @@
* All rights reserved.
*/
+#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
@@ -29,23 +30,6 @@ struct cmdline_token_ops cmdline_token_num_ops = {
.get_help = cmdline_get_help_num,
};
-enum num_parse_state_t {
- START,
- DEC_NEG,
- BIN,
- HEX,
-
- ERROR,
-
- FIRST_OK, /* not used */
- ZERO_OK,
- HEX_OK,
- OCTAL_OK,
- BIN_OK,
- DEC_NEG_OK,
- DEC_POS_OK,
-};
-
/* Keep it sync with enum in .h */
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
@@ -53,13 +37,13 @@ static const char * num_help[] = {
};
static inline int
-add_to_res(unsigned int c, uint64_t *res, unsigned int base)
+add_to_bin(unsigned int c, uint64_t *res)
{
/* overflow */
- if ((UINT64_MAX - c) / base < *res)
+ if ((UINT64_MAX - c) / 2 < *res)
return -1;
- *res = (uint64_t) (*res * base + c);
+ *res = (uint64_t) (*res * 2 + c);
return 0;
}
@@ -93,133 +77,106 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
return 0;
}
-/* parse an int */
-RTE_EXPORT_SYMBOL(cmdline_parse_num)
-int
-cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
- unsigned ressize)
+static int
+check_parsed_num(enum cmdline_numtype type, int neg, uint64_t uintres)
{
- struct cmdline_token_num_data nd;
- enum num_parse_state_t st = START;
+ int lo_ok, hi_ok;
+
+ switch (type) {
+ case RTE_UINT8:
+ if (neg || uintres > UINT8_MAX)
+ return -1;
+ return 0;
+ case RTE_UINT16:
+ if (neg || uintres > UINT16_MAX)
+ return -1;
+ return 0;
+ case RTE_UINT32:
+ if (neg || uintres > UINT32_MAX)
+ return -1;
+ return 0;
+ case RTE_UINT64:
+ if (neg)
+ return -1;
+ return 0;
+ case RTE_INT8:
+ lo_ok = !neg || (int64_t)uintres >= INT8_MIN;
+ hi_ok = neg || uintres <= INT8_MAX;
+ break;
+ case RTE_INT16:
+ lo_ok = !neg || (int64_t)uintres >= INT16_MIN;
+ hi_ok = neg || uintres <= INT16_MAX;
+ break;
+ case RTE_INT32:
+ lo_ok = !neg || (int64_t)uintres >= INT32_MIN;
+ hi_ok = neg || uintres <= INT32_MAX;
+ break;
+ case RTE_INT64:
+ lo_ok = 1; /* always valid */
+ hi_ok = neg || uintres <= INT64_MAX;
+ break;
+ default:
+ return -1;
+ }
+ /* check ranges */
+ if (!lo_ok || !hi_ok)
+ return -1;
+ return 0;
+}
+
+static int
+parse_num(const char *srcbuf, uint64_t *resptr)
+{
+ uint64_t uintres;
+ char *end;
+ int neg = *srcbuf == '-';
+
+ errno = 0;
+ if (neg)
+ /* for negatives, only support base-10 */
+ uintres = (uint64_t)strtoll(srcbuf, &end, 10);
+ else
+ /* 0 means autodetect base */
+ uintres = strtoull(srcbuf, &end, 0);
+
+ if (end == srcbuf || !cmdline_isendoftoken(*end) || errno == ERANGE)
+ return -1;
+ *resptr = uintres;
+ return end - srcbuf;
+}
+
+static int
+parse_bin(const char *srcbuf, uint64_t *res)
+{
+ uint64_t uintres = 0;
+ enum {
+ ERROR,
+ START,
+ BIN,
+ ZERO_OK,
+ BIN_OK,
+ } st = START;
const char * buf;
char c;
- uint64_t res1 = 0;
-
- if (!tk)
- return -1;
-
- if (!srcbuf || !*srcbuf)
- return -1;
buf = srcbuf;
c = *buf;
-
- memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
-
- /* check that we have enough room in res */
- if (res) {
- if (check_res_size(&nd, ressize) < 0)
- return -1;
- }
-
while (st != ERROR && c && !cmdline_isendoftoken(c)) {
debug_printf("%c %x -> ", c, c);
switch (st) {
case START:
- if (c == '-') {
- st = DEC_NEG;
- }
- else if (c == '0') {
+ if (c == '0') {
st = ZERO_OK;
}
- else if (c >= '1' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = DEC_POS_OK;
- }
- else {
+ else {
st = ERROR;
}
break;
case ZERO_OK:
- if (c == 'x') {
- st = HEX;
- }
- else if (c == 'b') {
+ if (c == 'b') {
st = BIN;
}
- else if (c >= '0' && c <= '7') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = OCTAL_OK;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_NEG:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = DEC_NEG_OK;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_NEG_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_POS_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
- case HEX:
- st = HEX_OK;
- /* fall-through */
- case HEX_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 16) < 0)
- st = ERROR;
- }
- else if (c >= 'a' && c <= 'f') {
- if (add_to_res(c - 'a' + 10, &res1, 16) < 0)
- st = ERROR;
- }
- else if (c >= 'A' && c <= 'F') {
- if (add_to_res(c - 'A' + 10, &res1, 16) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
-
- case OCTAL_OK:
- if (c >= '0' && c <= '7') {
- if (add_to_res(c - '0', &res1, 8) < 0)
- st = ERROR;
- }
else {
st = ERROR;
}
@@ -230,7 +187,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
/* fall-through */
case BIN_OK:
if (c >= '0' && c <= '1') {
- if (add_to_res(c - '0', &res1, 2) < 0)
+ if (add_to_bin(c - '0', &uintres) < 0)
st = ERROR;
}
else {
@@ -239,10 +196,10 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
break;
default:
debug_printf("not impl ");
-
+ st = ERROR;
}
- debug_printf("(%"PRIu64")\n", res1);
+ debug_printf("(%"PRIu64")\n", uintres);
buf ++;
c = *buf;
@@ -252,66 +209,90 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
return -1;
}
- switch (st) {
- case ZERO_OK:
- case DEC_POS_OK:
- case HEX_OK:
- case OCTAL_OK:
- case BIN_OK:
- if (nd.type == RTE_INT8 && res1 <= INT8_MAX) {
- if (res) *(int8_t *)res = (int8_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT16 && res1 <= INT16_MAX) {
- if (res) *(int16_t *)res = (int16_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT32 && res1 <= INT32_MAX) {
- if (res) *(int32_t *)res = (int32_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT64 && res1 <= INT64_MAX) {
- if (res) *(int64_t *)res = (int64_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT8 && res1 <= UINT8_MAX) {
- if (res) *(uint8_t *)res = (uint8_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT16 && res1 <= UINT16_MAX) {
- if (res) *(uint16_t *)res = (uint16_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT32 && res1 <= UINT32_MAX) {
- if (res) *(uint32_t *)res = (uint32_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT64) {
- if (res) *(uint64_t *)res = res1;
- return buf-srcbuf;
- } else {
+ if (st != BIN_OK)
+ return -1;
+
+ *res = uintres;
+ return buf - srcbuf;
+}
+
+/* parse an int */
+RTE_EXPORT_SYMBOL(cmdline_parse_num)
+int
+cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
+ unsigned ressize)
+{
+ struct cmdline_token_num_data nd;
+
+ if (!tk)
+ return -1;
+
+ if (!srcbuf || !*srcbuf)
+ return -1;
+
+ memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
+
+ /* check that we have enough room in res */
+ if (res) {
+ if (check_res_size(&nd, ressize) < 0)
return -1;
+ }
+
+ if (nd.type >= RTE_UINT8 && nd.type <= RTE_INT64) {
+ int ret, neg = *srcbuf == '-';
+ uint64_t uintres;
+
+ /*
+ * for backwards compatibility with previous iterations of
+ * cmdline library, we need to take into account a few things:
+ *
+ * - we only support negatives when they're decimal
+ * - we support binary which isn't supported by C parsers
+ * - strtoull does not do range checks on negative numbers
+ */
+ ret = parse_num(srcbuf, &uintres);
+
+ if (ret < 0) {
+ /* parse failed, try parsing as binary */
+ ret = parse_bin(srcbuf, &uintres);
+ if (ret < 0)
+ return -1;
}
- break;
-
- case DEC_NEG_OK:
- if (nd.type == RTE_INT8 &&
- res1 <= INT8_MAX + 1) {
- if (res) *(int8_t *)res = (int8_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT16 &&
- res1 <= (uint16_t)INT16_MAX + 1) {
- if (res) *(int16_t *)res = (int16_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT32 &&
- res1 <= (uint32_t)INT32_MAX + 1) {
- if (res) *(int32_t *)res = (int32_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT64 &&
- res1 <= (uint64_t)INT64_MAX + 1) {
- if (res) *(int64_t *)res = (int64_t) (-res1);
- return buf-srcbuf;
- } else {
+ /* check if we're within valid range */
+ if (check_parsed_num(nd.type, neg, uintres) < 0)
+ return -1;
+
+ switch (nd.type) {
+ case RTE_UINT8:
+ if (res) *(uint8_t *)res = (uint8_t)uintres;
+ break;
+ case RTE_UINT16:
+ if (res) *(uint16_t *)res = (uint16_t)uintres;
+ break;
+ case RTE_UINT32:
+ if (res) *(uint32_t *)res = (uint32_t)uintres;
+ break;
+ case RTE_UINT64:
+ if (res) *(uint64_t *)res = uintres;
+ break;
+ case RTE_INT8:
+ if (res) *(int8_t *)res = (int8_t)uintres;
+ break;
+ case RTE_INT16:
+ if (res) *(int16_t *)res = (int16_t)uintres;
+ break;
+ case RTE_INT32:
+ if (res) *(int32_t *)res = (int32_t)uintres;
+ break;
+ case RTE_INT64:
+ if (res) *(int64_t *)res = (int64_t)uintres;
+ break;
+ default:
return -1;
}
- break;
- default:
- debug_printf("error\n");
- return -1;
+ return ret;
}
+ return -1;
}
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v5 2/3] cmdline: add floating point support
2025-05-07 15:22 ` [PATCH v5 1/3] cmdline: use C standard library as number parser Anatoly Burakov
@ 2025-05-07 15:22 ` Anatoly Burakov
2025-05-07 15:22 ` [PATCH v5 3/3] app/testpmd: add sleep command Anatoly Burakov
2025-05-08 7:27 ` [PATCH v5 1/3] cmdline: use C standard library as number parser Bruce Richardson
2 siblings, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-07 15:22 UTC (permalink / raw)
To: dev
Add support for parsing floating point numbers in cmdline library, as well
as unit tests for the new functionality. Use C library for parsing.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v4 -> v5:
- Reworked to use standard C library functions as much as possible,
keeping near-100% compatibility with earlier versions (the only
difference is that strings like "+4" are now considered valid)
v3 -> v4:
- Removed unnecessary check for integer overflow when parsing negative
floats (as we convert to double before changing sign)
- Make naming of float exponent states more consistent
v2 -> v3:
- Fixed a bug where a free-standing negative exponent ("1e-") would attempt to be
parsed, and added unit tests for this case
- Added support for floats in dpdk-cmdline-gen script
- Added documentation updates to call out float support
app/test/test_cmdline_num.c | 205 ++++++++++++++++++++++++-
buildtools/dpdk-cmdline-gen.py | 24 +--
doc/guides/prog_guide/cmdline.rst | 3 +
doc/guides/rel_notes/release_25_07.rst | 5 +
lib/cmdline/cmdline_parse_num.c | 34 +++-
lib/cmdline/cmdline_parse_num.h | 4 +-
6 files changed, 255 insertions(+), 20 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index 471e9964ee..db3b286601 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -5,6 +5,8 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <rte_string_fns.h>
@@ -23,6 +25,11 @@ struct num_signed_str {
int64_t result;
};
+struct num_float_str {
+ const char * str;
+ double result;
+};
+
const struct num_unsigned_str num_valid_positive_strs[] = {
/* decimal positive */
{"0", 0 },
@@ -141,6 +148,63 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-9223372036854775808", INT64_MIN },
};
+const struct num_float_str num_valid_float_strs[] = {
+ /* zero */
+ {"0", 0},
+ /* parse int as float */
+ {"1", 1},
+ {"-1", -1},
+ /* fractional */
+ {"1.23", 1.23},
+ {"-1.23", -1.23},
+ {"0.123", 0.123},
+ {"-0.123", -0.123},
+ {"123.456", 123.456},
+ {"-123.456", -123.456},
+ /* positive exponent */
+ {"1e2", 1e2},
+ {"-1e2", -1e2},
+ {"1E2", 1E2},
+ {"-1E2", -1E2},
+ {"0.12e3", 0.12e3},
+ {"-0.12e3", -0.12e3},
+ {"1.23e4", 1.23e4},
+ {"-1.23e4", -1.23e4},
+ {"1.23E4", 1.23E4},
+ {"-1.23E4", -1.23E4},
+ {"123.456e7", 123.456e7},
+ {"-123.456e7", -123.456e7},
+ {"123.456E7", 123.456E7},
+ {"-123.456E7", -123.456E7},
+ /* negative exponent */
+ {"1e-2", 1e-2},
+ {"-1e-2", -1e-2},
+ {"1E-2", 1E-2},
+ {"-1E-2", -1E-2},
+ {"0.12e-3", 0.12e-3},
+ {"-0.12e-3", -0.12e-3},
+ {"1.23e-4", 1.23e-4},
+ {"-1.23e-4", -1.23e-4},
+ {"1.23E-4", 1.23E-4},
+ {"-1.23E-4", -1.23E-4},
+ {"123.456e-7", 123.456e-7},
+ {"-123.456e-7", -123.456e-7},
+ {"123.456E-7", 123.456E-7},
+ {"-123.456E-7", -123.456E-7},
+ /* try overflowing float */
+ {"2e63", 2e63},
+ {"-2e63", -2e63},
+ {"2E63", 2E63},
+ {"-2E63", -2E63},
+ {"18446744073709551615", (double) UINT64_MAX},
+ {"-9223372036854775808", (double) INT64_MIN},
+ /* try overflowing double */
+ {"2e308", HUGE_VAL},
+ {"-2e308", -HUGE_VAL},
+ {"2E308", HUGE_VAL},
+ {"-2E308", HUGE_VAL},
+};
+
const struct num_unsigned_str num_garbage_positive_strs[] = {
/* valid strings with garbage on the end, should still be valid */
/* decimal */
@@ -183,6 +247,26 @@ const struct num_signed_str num_garbage_negative_strs[] = {
{"-9223372036854775808 garbage", INT64_MIN },
};
+const char *float_invalid_strs[] = {
+ "1.1.",
+ "1.1.1",
+ "-1.1.",
+ "-1.1.1",
+ "e",
+ "1e",
+ "-1e",
+ "0.1e",
+ "-0.1e",
+ "1.e",
+ "-1.e",
+ "1.23e3.4",
+ "-1.23e3.4",
+ "1e1e",
+ "1e1e1",
+ "1e-",
+ "-1e-"
+};
+
const char * num_invalid_strs[] = {
"18446744073709551616", /* out of range unsigned */
"-9223372036854775809", /* out of range negative signed */
@@ -202,7 +286,16 @@ const char * num_invalid_strs[] = {
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
"1111000011110000111100001111000011110000111100001111000011110000"),
+ /* valid float values but should fail to parse as ints */
"1E3",
+ "-1E3",
+ "1.23",
+ "-1.23",
+ "1E-3",
+ "-1E-3",
+ "1.23E4",
+ "-1.23E4",
+ /* misc invalid values */
"0A",
"-B",
"1.23G",
@@ -215,6 +308,47 @@ const char * num_invalid_strs[] = {
"\0",
};
+static int
+float_cmp(double expected, void *actual_p, enum cmdline_numtype type)
+{
+ double eps;
+ double actual_d;
+
+ if (type == RTE_FLOAT_SINGLE) {
+ /* read as float, convert to double */
+ actual_d = (double)*(float *)actual_p;
+ /* FLT_EPSILON is too small for some tests */
+ eps = 1e-5f;
+ } else {
+ /* read as double */
+ actual_d = *(double *)actual_p;
+ eps = DBL_EPSILON;
+ }
+ /* compare using epsilon value */
+ if (fabs(expected - actual_d) < eps)
+ return 0;
+ /* not equal */
+ return expected < actual_d ? -1 : 1;
+}
+
+static int
+can_parse_float(double expected_result, enum cmdline_numtype type)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ if (expected_result > FLT_MAX || expected_result < -FLT_MAX)
+ return 0;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (expected_result > DBL_MAX || expected_result < -DBL_MAX)
+ return 0;
+ break;
+ default:
+ return 1;
+ }
+ return 1;
+}
+
static int
can_parse_unsigned(uint64_t expected_result, enum cmdline_numtype type)
{
@@ -370,11 +504,11 @@ test_parse_num_invalid_data(void)
int ret = 0;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result; /* pick largest buffer */
cmdline_parse_token_num_t token;
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result; /* pick largest buffer */
token.num_data.type = type;
/* test full strings */
@@ -396,6 +530,31 @@ test_parse_num_invalid_data(void)
}
}
}
+
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result; /* pick largest buffer */
+ token.num_data.type = type;
+
+ /* test full strings */
+ for (i = 0; i < RTE_DIM(float_invalid_strs); i++) {
+
+ memset(&result, 0, sizeof(double));
+ memset(&buf, 0, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*)&token,
+ float_invalid_strs[i], (void*)&result, sizeof(result));
+ if (ret != -1) {
+ /* get some info about what we are trying to parse */
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ printf("Error: parsing %s as %s succeeded!\n",
+ float_invalid_strs[i], buf);
+ return -1;
+ }
+ }
+ }
return 0;
}
@@ -407,13 +566,13 @@ test_parse_num_valid(void)
enum cmdline_numtype type;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result;
cmdline_parse_token_num_t token;
/** valid strings **/
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive strings */
@@ -452,7 +611,7 @@ test_parse_num_valid(void)
cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
buf, sizeof(buf));
- ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
num_valid_negative_strs[i].str,
(void*)&result, sizeof(result));
@@ -488,10 +647,44 @@ test_parse_num_valid(void)
}
}
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(num_valid_float_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ num_valid_float_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(num_valid_float_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ num_valid_float_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(num_valid_float_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ num_valid_float_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
/** garbage strings **/
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive garbage strings */
diff --git a/buildtools/dpdk-cmdline-gen.py b/buildtools/dpdk-cmdline-gen.py
index 7dadded783..6c76d7116a 100755
--- a/buildtools/dpdk-cmdline-gen.py
+++ b/buildtools/dpdk-cmdline-gen.py
@@ -17,16 +17,18 @@
RTE_SET_USED(cl);
RTE_SET_USED(data);
"""
-NUMERIC_TYPES = [
- "UINT8",
- "UINT16",
- "UINT32",
- "UINT64",
- "INT8",
- "INT16",
- "INT32",
- "INT64",
-]
+NUMERIC_TYPES = {
+ "UINT8": "uint8_t",
+ "UINT16": "uint16_t",
+ "UINT32": "uint32_t",
+ "UINT64": "uint64_t",
+ "INT8": "int8_t",
+ "INT16": "int16_t",
+ "INT32": "int32_t",
+ "INT64": "int64_t",
+ "FLOAT_SINGLE": "float",
+ "FLOAT_DOUBLE": "double",
+}
def process_command(lineno, tokens, comment):
@@ -70,7 +72,7 @@ def process_command(lineno, tokens, comment):
f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});"
)
elif t_type in NUMERIC_TYPES:
- result_struct.append(f"\t{t_type.lower()}_t {t_name};")
+ result_struct.append(f"\t{NUMERIC_TYPES[t_type]} {t_name};")
initializers.append(
f"static cmdline_parse_token_num_t cmd_{name}_{t_name}_tok =\n"
f"\tTOKEN_NUM_INITIALIZER(struct cmd_{name}_result, {t_name}, RTE_{t_type});"
diff --git a/doc/guides/prog_guide/cmdline.rst b/doc/guides/prog_guide/cmdline.rst
index e20281ceb5..447a90e32e 100644
--- a/doc/guides/prog_guide/cmdline.rst
+++ b/doc/guides/prog_guide/cmdline.rst
@@ -22,6 +22,7 @@ The DPDK command-line library supports the following features:
* Strings
* Signed/unsigned 16/32/64-bit integers
+ * Single/double precision floats
* IP Addresses
* Ethernet Addresses
@@ -68,6 +69,8 @@ The format of the list file must be:
* ``<UINT16>port_id``
+ * ``<FLOAT_SINGLE>ratio``
+
* ``<IP>src_ip``
* ``<IPv4>dst_ip4``
diff --git a/doc/guides/rel_notes/release_25_07.rst b/doc/guides/rel_notes/release_25_07.rst
index 093b85d206..54bc545110 100644
--- a/doc/guides/rel_notes/release_25_07.rst
+++ b/doc/guides/rel_notes/release_25_07.rst
@@ -55,6 +55,11 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added floating point numbers support to cmdline library.**
+
+ The cmdline library now supports parsing single- and double-precision
+ floating point numbers in interactive user commands.
+
Removed Items
-------------
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index f7ccd26d96..4ffc58aeb5 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -8,6 +8,8 @@
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <string.h>
#include <eal_export.h>
#include <rte_string_fns.h>
@@ -34,6 +36,7 @@ struct cmdline_token_ops cmdline_token_num_ops = {
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
"INT8", "INT16", "INT32", "INT64",
+ "FLOAT_SINGLE", "FLOAT_DOUBLE"
};
static inline int
@@ -71,6 +74,14 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
if (ressize < sizeof(int64_t))
return -1;
break;
+ case RTE_FLOAT_SINGLE:
+ if (ressize < sizeof(float))
+ return -1;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (ressize < sizeof(double))
+ return -1;
+ break;
default:
return -1;
}
@@ -216,7 +227,7 @@ parse_bin(const char *srcbuf, uint64_t *res)
return buf - srcbuf;
}
-/* parse an int */
+/* parse a number */
RTE_EXPORT_SYMBOL(cmdline_parse_num)
int
cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
@@ -237,7 +248,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
if (check_res_size(&nd, ressize) < 0)
return -1;
}
-
+ /* integer parsing */
if (nd.type >= RTE_UINT8 && nd.type <= RTE_INT64) {
int ret, neg = *srcbuf == '-';
uint64_t uintres;
@@ -291,6 +302,25 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
return -1;
}
return ret;
+ /* float parsing */
+ } else if (nd.type >= RTE_FLOAT_SINGLE && nd.type <= RTE_FLOAT_DOUBLE) {
+ char *end;
+ double dres = strtod(srcbuf, &end);
+
+ if (end == srcbuf || !cmdline_isendoftoken(*end) || isinf(dres))
+ return -1;
+
+ /* we parsed something, now let's ensure it fits */
+ if (nd.type == RTE_FLOAT_SINGLE) {
+ float flt = (float)dres;
+ if (isinf(flt))
+ return -1;
+ if (res) *(float *)res = flt;
+ return end-srcbuf;
+ } else if (nd.type == RTE_FLOAT_DOUBLE) {
+ if (res) *(double *)res = dres;
+ return end-srcbuf;
+ }
}
return -1;
}
diff --git a/lib/cmdline/cmdline_parse_num.h b/lib/cmdline/cmdline_parse_num.h
index bdd0267612..b2792a2d11 100644
--- a/lib/cmdline/cmdline_parse_num.h
+++ b/lib/cmdline/cmdline_parse_num.h
@@ -22,7 +22,9 @@ enum cmdline_numtype {
RTE_INT8,
RTE_INT16,
RTE_INT32,
- RTE_INT64
+ RTE_INT64,
+ RTE_FLOAT_SINGLE,
+ RTE_FLOAT_DOUBLE,
};
struct cmdline_token_num_data {
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v5 3/3] app/testpmd: add sleep command
2025-05-07 15:22 ` [PATCH v5 1/3] cmdline: use C standard library as number parser Anatoly Burakov
2025-05-07 15:22 ` [PATCH v5 2/3] cmdline: add floating point support Anatoly Burakov
@ 2025-05-07 15:22 ` Anatoly Burakov
2025-05-08 7:27 ` [PATCH v5 1/3] cmdline: use C standard library as number parser Bruce Richardson
2 siblings, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-07 15:22 UTC (permalink / raw)
To: dev, Aman Singh
Test-pmd already has a way to run a list of commands from file, but there
is no way to pause execution for a specified amount of time between two
commands. This may be necessary for simple automation, particularly for
waiting on some asynchronous operation such as link status update.
Add a simple sleep command to wait until certain number of seconds has
passed, using the newly added cmdline library floating point support.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v1 -> v2:
- Add floating point support to cmdline
- Use floating point format for pause command
app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index d4bb3ec998..b6152c07e6 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
"quit\n"
" Quit to prompt.\n\n"
+
+ "sleep secs\n"
+ " Sleep for secs seconds (can be fractional).\n\n"
);
}
@@ -7768,6 +7771,37 @@ static cmdline_parse_inst_t cmd_quit = {
},
};
+/* *** SLEEP *** */
+struct cmd_sleep_result {
+ cmdline_fixed_string_t sleep;
+ double secs;
+};
+
+static void cmd_sleep_parsed(void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_sleep_result *res = parsed_result;
+
+ rte_delay_us_sleep(res->secs * 1E6);
+}
+
+static cmdline_parse_token_string_t cmd_sleep_sleep =
+ TOKEN_STRING_INITIALIZER(struct cmd_sleep_result, sleep, "sleep");
+static cmdline_parse_token_num_t cmd_sleep_seconds =
+ TOKEN_NUM_INITIALIZER(struct cmd_sleep_result, secs, RTE_FLOAT_DOUBLE);
+
+static cmdline_parse_inst_t cmd_sleep = {
+ .f = cmd_sleep_parsed,
+ .data = NULL,
+ .help_str = "sleep <secs>: Sleep for a specified number of seconds",
+ .tokens = {
+ (void *)&cmd_sleep_sleep,
+ (void *)&cmd_sleep_seconds,
+ NULL,
+ },
+};
+
/* *** ADD/REMOVE MAC ADDRESS FROM A PORT *** */
struct cmd_mac_addr_result {
cmdline_fixed_string_t mac_addr_cmd;
@@ -13711,6 +13745,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_showdevice,
&cmd_showcfg,
&cmd_showfwdall,
+ &cmd_sleep,
&cmd_start,
&cmd_start_tx_first,
&cmd_start_tx_first_n,
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v5 1/3] cmdline: use C standard library as number parser
2025-05-07 15:22 ` [PATCH v5 1/3] cmdline: use C standard library as number parser Anatoly Burakov
2025-05-07 15:22 ` [PATCH v5 2/3] cmdline: add floating point support Anatoly Burakov
2025-05-07 15:22 ` [PATCH v5 3/3] app/testpmd: add sleep command Anatoly Burakov
@ 2025-05-08 7:27 ` Bruce Richardson
2025-05-08 8:35 ` Burakov, Anatoly
2 siblings, 1 reply; 45+ messages in thread
From: Bruce Richardson @ 2025-05-08 7:27 UTC (permalink / raw)
To: Anatoly Burakov; +Cc: dev
On Wed, May 07, 2025 at 04:22:10PM +0100, Anatoly Burakov wrote:
> Remove custom number parser and use C standard library instead. In order to
> keep compatibility with earlier versions of the parser, we have to take
> into account a few quirks:
>
> - We do not consider "negative" numbers to be valid for anything other than
> base-10 numbers, whereas C standard library does. We work around that by
> forcing base-10 when parsing numbers we expect to be negative.
Is it likely to break much in the way of compatibility if we start allowing
negative non-base-10 numbers? If it simplifies our code to allow it, I'd
tend towards doing so unless there is a known case where it might cause
problems.
/Bruce
> - We do not consider numbers such as "+4" to be valid, whereas C standard
> library does. No one probably relies on this, so we just remove it from
> our tests, as it is now a valid number.
> - C standard library's strtoull does not do range checks on negative
> numbers, so we have to parse knowingly-negative numbers as signed. We've
> already changed this to be the case on account of point 1 from this list,
> so no biggie.
> - C standard library does not support binary numbers, so we keep around the
> relevant parts of the custom parser in place to support binary numbers.
>
> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> ---
<snip>
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v5 1/3] cmdline: use C standard library as number parser
2025-05-08 7:27 ` [PATCH v5 1/3] cmdline: use C standard library as number parser Bruce Richardson
@ 2025-05-08 8:35 ` Burakov, Anatoly
0 siblings, 0 replies; 45+ messages in thread
From: Burakov, Anatoly @ 2025-05-08 8:35 UTC (permalink / raw)
To: Bruce Richardson; +Cc: dev
On 5/8/2025 9:27 AM, Bruce Richardson wrote:
> On Wed, May 07, 2025 at 04:22:10PM +0100, Anatoly Burakov wrote:
>> Remove custom number parser and use C standard library instead. In order to
>> keep compatibility with earlier versions of the parser, we have to take
>> into account a few quirks:
>>
>> - We do not consider "negative" numbers to be valid for anything other than
>> base-10 numbers, whereas C standard library does. We work around that by
>> forcing base-10 when parsing numbers we expect to be negative.
>
> Is it likely to break much in the way of compatibility if we start allowing
> negative non-base-10 numbers? If it simplifies our code to allow it, I'd
> tend towards doing so unless there is a known case where it might cause
> problems.
>
> /Bruce
>
It wouldn't simplify much because we still need to use strtoll when
parsing negative numbers, because strtoull doesn't do range checks on
negative values. So it'll literally be a one line difference (use base 0
- autodetect - instead of base 10 when calling strtoll), and fewer
"invalid" tests. I can do that just for consistency sake but it's not
going to be a huge difference in terms of code.
--
Thanks,
Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v6 1/3] cmdline: use C standard library as number parser
2025-05-02 12:27 [PATCH v1 1/1] app/testpmd: add sleep command Anatoly Burakov
` (5 preceding siblings ...)
2025-05-07 15:22 ` [PATCH v5 1/3] cmdline: use C standard library as number parser Anatoly Burakov
@ 2025-05-08 9:53 ` Anatoly Burakov
2025-05-08 9:53 ` [PATCH v6 2/3] cmdline: add floating point support Anatoly Burakov
2025-05-08 9:53 ` [PATCH v6 3/3] app/testpmd: add sleep command Anatoly Burakov
2025-05-08 10:01 ` [PATCH v7 1/3] cmdline: use C standard library as number parser Anatoly Burakov
` (3 subsequent siblings)
10 siblings, 2 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-08 9:53 UTC (permalink / raw)
To: dev
Remove custom number parser and use C standard library instead. In order to
keep compatibility with earlier versions of the parser, we have to take
into account a couple of quirks:
- We did not consider "negative" numbers to be valid for anything other
than base-10 numbers, whereas C standard library does. Adjust the tests
to match the new behavior.
- We did not consider numbers such as "+4" to be valid, whereas C
standard library does. Adjust the tests to match the new behavior.
- C standard library's strtoull does not do range checks on negative
numbers, so we have to parse knowingly-negative numbers as signed.
- C standard library does not support binary numbers, so we keep around the
relevant parts of the custom parser in place to support binary numbers.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v5 -> v6:
- Allowed more negative numbers (such as negative octals or hex)
- Updated unit tests to check new cases
- Small refactoring of code to reduce amount of noise
- More verbose debug output
v4 -> v5:
- Added this commit
app/test/test_cmdline_num.c | 19 +-
lib/cmdline/cmdline_parse_num.c | 364 ++++++++++++++++----------------
2 files changed, 195 insertions(+), 188 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index 9276de59bd..dd70f6f824 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -139,6 +139,8 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-2147483648", INT32_MIN },
{"-2147483649", INT32_MIN - 1LL },
{"-9223372036854775808", INT64_MIN },
+ {"-0x8000000000000000", INT64_MIN },
+ {"-01000000000000000000000", INT64_MIN },
};
const struct num_unsigned_str num_garbage_positive_strs[] = {
@@ -175,12 +177,27 @@ const struct num_unsigned_str num_garbage_positive_strs[] = {
const struct num_signed_str num_garbage_negative_strs[] = {
/* valid strings with garbage on the end, should still be valid */
+ /* negative numbers */
{"-9223372036854775808\0garbage", INT64_MIN },
{"-9223372036854775808\rgarbage", INT64_MIN },
{"-9223372036854775808\tgarbage", INT64_MIN },
{"-9223372036854775808\ngarbage", INT64_MIN },
{"-9223372036854775808#garbage", INT64_MIN },
{"-9223372036854775808 garbage", INT64_MIN },
+ /* negative hex */
+ {"-0x8000000000000000\0garbage", INT64_MIN },
+ {"-0x8000000000000000\rgarbage", INT64_MIN },
+ {"-0x8000000000000000\tgarbage", INT64_MIN },
+ {"-0x8000000000000000\ngarbage", INT64_MIN },
+ {"-0x8000000000000000#garbage", INT64_MIN },
+ {"-0x8000000000000000 garbage", INT64_MIN },
+ /* negative octal */
+ {"-01000000000000000000000\0garbage", INT64_MIN },
+ {"-01000000000000000000000\rgarbage", INT64_MIN },
+ {"-01000000000000000000000\tgarbage", INT64_MIN },
+ {"-01000000000000000000000\ngarbage", INT64_MIN },
+ {"-01000000000000000000000#garbage", INT64_MIN },
+ {"-01000000000000000000000 garbage", INT64_MIN },
};
const char * num_invalid_strs[] = {
@@ -197,7 +214,6 @@ const char * num_invalid_strs[] = {
"0b01110101017001",
/* false negative numbers */
"-12345F623",
- "-0x1234580A",
"-0b0111010101",
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
@@ -205,7 +221,6 @@ const char * num_invalid_strs[] = {
"1E3",
"0A",
"-B",
- "+4",
"1.23G",
"",
" ",
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index f21796bedb..ff2bdc28c8 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -4,6 +4,7 @@
* All rights reserved.
*/
+#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
@@ -29,23 +30,6 @@ struct cmdline_token_ops cmdline_token_num_ops = {
.get_help = cmdline_get_help_num,
};
-enum num_parse_state_t {
- START,
- DEC_NEG,
- BIN,
- HEX,
-
- ERROR,
-
- FIRST_OK, /* not used */
- ZERO_OK,
- HEX_OK,
- OCTAL_OK,
- BIN_OK,
- DEC_NEG_OK,
- DEC_POS_OK,
-};
-
/* Keep it sync with enum in .h */
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
@@ -53,13 +37,13 @@ static const char * num_help[] = {
};
static inline int
-add_to_res(unsigned int c, uint64_t *res, unsigned int base)
+add_to_bin(unsigned int c, uint64_t *res)
{
/* overflow */
- if ((UINT64_MAX - c) / base < *res)
+ if ((UINT64_MAX - c) / 2 < *res)
return -1;
- *res = (uint64_t) (*res * base + c);
+ *res = (uint64_t) (*res * 2 + c);
return 0;
}
@@ -88,138 +72,119 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
return -1;
break;
default:
+ debug_printf("Check size: unsupported number format: %s\n",
+ num_help[nd->type]);
return -1;
}
return 0;
}
-/* parse an int */
-RTE_EXPORT_SYMBOL(cmdline_parse_num)
-int
-cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
- unsigned ressize)
+static int
+check_parsed_num(enum cmdline_numtype type, int neg, uint64_t uintres)
{
- struct cmdline_token_num_data nd;
- enum num_parse_state_t st = START;
+ int lo_ok, hi_ok;
+
+ switch (type) {
+ case RTE_UINT8:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT8_MAX;
+ break;
+ case RTE_UINT16:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT16_MAX;
+ break;
+ case RTE_UINT32:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT32_MAX;
+ break;
+ case RTE_UINT64:
+ lo_ok = !neg;
+ hi_ok = 1; /* can't be out of range if parsed successfully */
+ break;
+ case RTE_INT8:
+ lo_ok = !neg || (int64_t)uintres >= INT8_MIN;
+ hi_ok = neg || uintres <= INT8_MAX;
+ break;
+ case RTE_INT16:
+ lo_ok = !neg || (int64_t)uintres >= INT16_MIN;
+ hi_ok = neg || uintres <= INT16_MAX;
+ break;
+ case RTE_INT32:
+ lo_ok = !neg || (int64_t)uintres >= INT32_MIN;
+ hi_ok = neg || uintres <= INT32_MAX;
+ break;
+ case RTE_INT64:
+ lo_ok = 1; /* can't be out of range if parsed successfully */
+ hi_ok = neg || uintres <= INT64_MAX;
+ break;
+ default:
+ debug_printf("Check range failed: unsupported number format: %s\n",
+ num_help[type]);
+ return -1;
+ }
+ /* check ranges */
+ if (!lo_ok || !hi_ok)
+ return -1;
+ return 0;
+}
+
+static int
+parse_num(const char *srcbuf, uint64_t *resptr)
+{
+ uint64_t uintres;
+ char *end;
+ int neg = *srcbuf == '-';
+
+ /*
+ * strtoull does not do range checks on negative numbers, so we need to
+ * use strtoll if we know the value we're parsing looks like a negative
+ * one. we use base 0 for both, 0 means autodetect base.
+ */
+ errno = 0;
+ if (neg)
+ uintres = (uint64_t)strtoll(srcbuf, &end, 0);
+ else
+ uintres = strtoull(srcbuf, &end, 0);
+
+ if (end == srcbuf || !cmdline_isendoftoken(*end) || errno == ERANGE)
+ return -1;
+
+ *resptr = uintres;
+ return end - srcbuf;
+}
+
+static int
+parse_bin(const char *srcbuf, uint64_t *res)
+{
+ uint64_t uintres = 0;
+ enum {
+ ERROR,
+ START,
+ BIN,
+ ZERO_OK,
+ BIN_OK,
+ } st = START;
const char * buf;
char c;
- uint64_t res1 = 0;
-
- if (!tk)
- return -1;
-
- if (!srcbuf || !*srcbuf)
- return -1;
buf = srcbuf;
c = *buf;
-
- memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
-
- /* check that we have enough room in res */
- if (res) {
- if (check_res_size(&nd, ressize) < 0)
- return -1;
- }
-
while (st != ERROR && c && !cmdline_isendoftoken(c)) {
debug_printf("%c %x -> ", c, c);
switch (st) {
case START:
- if (c == '-') {
- st = DEC_NEG;
- }
- else if (c == '0') {
+ if (c == '0') {
st = ZERO_OK;
}
- else if (c >= '1' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = DEC_POS_OK;
- }
- else {
+ else {
st = ERROR;
}
break;
case ZERO_OK:
- if (c == 'x') {
- st = HEX;
- }
- else if (c == 'b') {
+ if (c == 'b') {
st = BIN;
}
- else if (c >= '0' && c <= '7') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = OCTAL_OK;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_NEG:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = DEC_NEG_OK;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_NEG_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_POS_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
- case HEX:
- st = HEX_OK;
- /* fall-through */
- case HEX_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 16) < 0)
- st = ERROR;
- }
- else if (c >= 'a' && c <= 'f') {
- if (add_to_res(c - 'a' + 10, &res1, 16) < 0)
- st = ERROR;
- }
- else if (c >= 'A' && c <= 'F') {
- if (add_to_res(c - 'A' + 10, &res1, 16) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
-
- case OCTAL_OK:
- if (c >= '0' && c <= '7') {
- if (add_to_res(c - '0', &res1, 8) < 0)
- st = ERROR;
- }
else {
st = ERROR;
}
@@ -230,7 +195,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
/* fall-through */
case BIN_OK:
if (c >= '0' && c <= '1') {
- if (add_to_res(c - '0', &res1, 2) < 0)
+ if (add_to_bin(c - '0', &uintres) < 0)
st = ERROR;
}
else {
@@ -239,10 +204,10 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
break;
default:
debug_printf("not impl ");
-
+ st = ERROR;
}
- debug_printf("(%"PRIu64")\n", res1);
+ debug_printf("(%"PRIu64")\n", uintres);
buf ++;
c = *buf;
@@ -252,66 +217,93 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
return -1;
}
- switch (st) {
- case ZERO_OK:
- case DEC_POS_OK:
- case HEX_OK:
- case OCTAL_OK:
- case BIN_OK:
- if (nd.type == RTE_INT8 && res1 <= INT8_MAX) {
- if (res) *(int8_t *)res = (int8_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT16 && res1 <= INT16_MAX) {
- if (res) *(int16_t *)res = (int16_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT32 && res1 <= INT32_MAX) {
- if (res) *(int32_t *)res = (int32_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT64 && res1 <= INT64_MAX) {
- if (res) *(int64_t *)res = (int64_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT8 && res1 <= UINT8_MAX) {
- if (res) *(uint8_t *)res = (uint8_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT16 && res1 <= UINT16_MAX) {
- if (res) *(uint16_t *)res = (uint16_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT32 && res1 <= UINT32_MAX) {
- if (res) *(uint32_t *)res = (uint32_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT64) {
- if (res) *(uint64_t *)res = res1;
- return buf-srcbuf;
- } else {
- return -1;
- }
- break;
+ if (st != BIN_OK)
+ return -1;
+
+ *res = uintres;
+ return buf - srcbuf;
+}
- case DEC_NEG_OK:
- if (nd.type == RTE_INT8 &&
- res1 <= INT8_MAX + 1) {
- if (res) *(int8_t *)res = (int8_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT16 &&
- res1 <= (uint16_t)INT16_MAX + 1) {
- if (res) *(int16_t *)res = (int16_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT32 &&
- res1 <= (uint32_t)INT32_MAX + 1) {
- if (res) *(int32_t *)res = (int32_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT64 &&
- res1 <= (uint64_t)INT64_MAX + 1) {
- if (res) *(int64_t *)res = (int64_t) (-res1);
- return buf-srcbuf;
- } else {
- return -1;
- }
+static int
+write_num(enum cmdline_numtype type, void *res, uint64_t uintres)
+{
+ switch (type) {
+ case RTE_UINT8:
+ *(uint8_t *)res = (uint8_t)uintres;
+ break;
+ case RTE_UINT16:
+ *(uint16_t *)res = (uint16_t)uintres;
+ break;
+ case RTE_UINT32:
+ *(uint32_t *)res = (uint32_t)uintres;
+ break;
+ case RTE_UINT64:
+ *(uint64_t *)res = uintres;
+ break;
+ case RTE_INT8:
+ *(int8_t *)res = (int8_t)uintres;
+ break;
+ case RTE_INT16:
+ *(int16_t *)res = (int16_t)uintres;
+ break;
+ case RTE_INT32:
+ *(int32_t *)res = (int32_t)uintres;
+ break;
+ case RTE_INT64:
+ *(int64_t *)res = (int64_t)uintres;
break;
default:
- debug_printf("error\n");
+ debug_printf("Write failed: unsupported number format: %s\n",
+ num_help[type]);
return -1;
}
+ return 0;
+}
+
+/* parse an int */
+RTE_EXPORT_SYMBOL(cmdline_parse_num)
+int
+cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
+ unsigned ressize)
+{
+ struct cmdline_token_num_data nd;
+
+ if (!tk)
+ return -1;
+
+ if (!srcbuf || !*srcbuf)
+ return -1;
+
+ memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
+
+ /* check that we have enough room in res */
+ if (res && check_res_size(&nd, ressize) < 0)
+ return -1;
+
+ if (nd.type >= RTE_UINT8 && nd.type <= RTE_INT64) {
+ int ret, neg = *srcbuf == '-';
+ uint64_t uintres;
+
+ /* try parsing as number */
+ ret = parse_num(srcbuf, &uintres);
+
+ if (ret < 0) {
+ /* parse failed, try parsing as binary */
+ ret = parse_bin(srcbuf, &uintres);
+ if (ret < 0)
+ return -1;
+ }
+ /* check if we're within valid range */
+ if (check_parsed_num(nd.type, neg, uintres) < 0)
+ return -1;
+
+ /* parsing succeeded, write the value if necessary */
+ if (res && write_num(nd.type, res, uintres) < 0)
+ return -1;
+
+ return ret;
+ }
+ return -1;
}
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v6 2/3] cmdline: add floating point support
2025-05-08 9:53 ` [PATCH v6 " Anatoly Burakov
@ 2025-05-08 9:53 ` Anatoly Burakov
2025-05-08 9:53 ` [PATCH v6 3/3] app/testpmd: add sleep command Anatoly Burakov
1 sibling, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-08 9:53 UTC (permalink / raw)
To: dev
Add support for parsing floating point numbers in cmdline library, as well
as unit tests for the new functionality. Use C library for parsing.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v5 -> v6:
- Small refactor to reduce amount of noise
- Use strtof for parsing single precision floats
- More unit tests
v4 -> v5:
- Reworked to use standard C library functions as much as possible,
keeping near-100% compatibility with earlier versions (the only
difference is that strings like "+4" are now considered valid)
v3 -> v4:
- Removed unnecessary check for integer overflow when parsing negative
floats (as we convert to double before changing sign)
- Make naming of float exponent states more consistent
v2 -> v3:
- Fixed a bug where a free-standing negative exponent ("1e-") would attempt to be
parsed, and added unit tests for this case
- Added support for floats in dpdk-cmdline-gen script
- Added documentation updates to call out float support
app/test/test_cmdline_num.c | 282 ++++++++++++++++++++++++-
buildtools/dpdk-cmdline-gen.py | 24 ++-
doc/guides/prog_guide/cmdline.rst | 3 +
doc/guides/rel_notes/release_25_07.rst | 5 +
lib/cmdline/cmdline_parse_num.c | 67 +++++-
lib/cmdline/cmdline_parse_num.h | 4 +-
6 files changed, 366 insertions(+), 19 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index dd70f6f824..183b103d5d 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -5,6 +5,8 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <rte_string_fns.h>
@@ -23,6 +25,11 @@ struct num_signed_str {
int64_t result;
};
+struct num_float_str {
+ const char * str;
+ double result;
+};
+
const struct num_unsigned_str num_valid_positive_strs[] = {
/* decimal positive */
{"0", 0 },
@@ -143,6 +150,63 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-01000000000000000000000", INT64_MIN },
};
+const struct num_float_str float_valid_strs[] = {
+ /* zero */
+ {"0", 0},
+ /* parse int as float */
+ {"1", 1},
+ {"-1", -1},
+ /* fractional */
+ {"1.23", 1.23},
+ {"-1.23", -1.23},
+ {"0.123", 0.123},
+ {"-0.123", -0.123},
+ {"123.456", 123.456},
+ {"-123.456", -123.456},
+ /* positive exponent */
+ {"1e2", 1e2},
+ {"-1e2", -1e2},
+ {"1E2", 1E2},
+ {"-1E2", -1E2},
+ {"0.12e3", 0.12e3},
+ {"-0.12e3", -0.12e3},
+ {"1.23e4", 1.23e4},
+ {"-1.23e4", -1.23e4},
+ {"1.23E4", 1.23E4},
+ {"-1.23E4", -1.23E4},
+ {"123.456e7", 123.456e7},
+ {"-123.456e7", -123.456e7},
+ {"123.456E7", 123.456E7},
+ {"-123.456E7", -123.456E7},
+ /* negative exponent */
+ {"1e-2", 1e-2},
+ {"-1e-2", -1e-2},
+ {"1E-2", 1E-2},
+ {"-1E-2", -1E-2},
+ {"0.12e-3", 0.12e-3},
+ {"-0.12e-3", -0.12e-3},
+ {"1.23e-4", 1.23e-4},
+ {"-1.23e-4", -1.23e-4},
+ {"1.23E-4", 1.23E-4},
+ {"-1.23E-4", -1.23E-4},
+ {"123.456e-7", 123.456e-7},
+ {"-123.456e-7", -123.456e-7},
+ {"123.456E-7", 123.456E-7},
+ {"-123.456E-7", -123.456E-7},
+ /* try overflowing float */
+ {"2e63", 2e63},
+ {"-2e63", -2e63},
+ {"2E63", 2E63},
+ {"-2E63", -2E63},
+ {"18446744073709551615", (double) UINT64_MAX},
+ {"-9223372036854775808", (double) INT64_MIN},
+ /* try overflowing double */
+ {"2e308", HUGE_VAL},
+ {"-2e308", -HUGE_VAL},
+ {"2E308", HUGE_VAL},
+ {"-2E308", HUGE_VAL},
+};
+
const struct num_unsigned_str num_garbage_positive_strs[] = {
/* valid strings with garbage on the end, should still be valid */
/* decimal */
@@ -200,6 +264,71 @@ const struct num_signed_str num_garbage_negative_strs[] = {
{"-01000000000000000000000 garbage", INT64_MIN },
};
+const struct num_float_str float_garbage_strs[] = {
+ /* valid strings with garbage on the end, should still be valid */
+ /* positive float positive exponent */
+ {"123.456e7\0garbage", 123.456e7},
+ {"123.456e7\rgarbage", 123.456e7},
+ {"123.456e7\tgarbage", 123.456e7},
+ {"123.456e7\ngarbage", 123.456e7},
+ {"123.456e7#garbage", 123.456e7},
+ {"123.456e7 garbage", 123.456e7},
+ /* negative float positive exponent */
+ {"-123.456e7\0garbage", -123.456e7},
+ {"-123.456e7\rgarbage", -123.456e7},
+ {"-123.456e7\tgarbage", -123.456e7},
+ {"-123.456e7\ngarbage", -123.456e7},
+ {"-123.456e7#garbage", -123.456e7},
+ {"-123.456e7 garbage", -123.456e7},
+ /* positive float negative exponent */
+ {"123.456e-7\0garbage", 123.456e-7},
+ {"123.456e-7\rgarbage", 123.456e-7},
+ {"123.456e-7\tgarbage", 123.456e-7},
+ {"123.456e-7\ngarbage", 123.456e-7},
+ {"123.456e-7#garbage", 123.456e-7},
+ {"123.456e-7 garbage", 123.456e-7},
+ /* negative float negative exponent */
+ {"-123.456e-7\0garbage", -123.456e-7},
+ {"-123.456e-7\rgarbage", -123.456e-7},
+ {"-123.456e-7\tgarbage", -123.456e-7},
+ {"-123.456e-7\ngarbage", -123.456e-7},
+ {"-123.456e-7#garbage", -123.456e-7},
+ {"-123.456e-7 garbage", -123.456e-7},
+ /* float overflows */
+ {"18446744073709551615\0garbage", (double) UINT64_MAX},
+ {"18446744073709551615\rgarbage", (double) UINT64_MAX},
+ {"18446744073709551615\ngarbage", (double) UINT64_MAX},
+ {"18446744073709551615\tgarbage", (double) UINT64_MAX},
+ {"18446744073709551615#garbage", (double) UINT64_MAX},
+ {"18446744073709551615 garbage", (double) UINT64_MAX},
+ {"-9223372036854775808\0garbage", (double) INT64_MIN},
+ {"-9223372036854775808\rgarbage", (double) INT64_MIN},
+ {"-9223372036854775808\ngarbage", (double) INT64_MIN},
+ {"-9223372036854775808\tgarbage", (double) INT64_MIN},
+ {"-9223372036854775808#garbage", (double) INT64_MIN},
+ {"-9223372036854775808 garbage", (double) INT64_MIN},
+};
+
+const char *float_invalid_strs[] = {
+ "1.1.",
+ "1.1.1",
+ "-1.1.",
+ "-1.1.1",
+ "e",
+ "1e",
+ "-1e",
+ "0.1e",
+ "-0.1e",
+ "1.e",
+ "-1.e",
+ "1.23e3.4",
+ "-1.23e3.4",
+ "1e1e",
+ "1e1e1",
+ "1e-",
+ "-1e-"
+};
+
const char * num_invalid_strs[] = {
"18446744073709551616", /* out of range unsigned */
"-9223372036854775809", /* out of range negative signed */
@@ -218,7 +347,16 @@ const char * num_invalid_strs[] = {
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
"1111000011110000111100001111000011110000111100001111000011110000"),
+ /* valid float values but should fail to parse as ints */
"1E3",
+ "-1E3",
+ "1.23",
+ "-1.23",
+ "1E-3",
+ "-1E-3",
+ "1.23E4",
+ "-1.23E4",
+ /* misc invalid values */
"0A",
"-B",
"1.23G",
@@ -231,6 +369,47 @@ const char * num_invalid_strs[] = {
"\0",
};
+static int
+float_cmp(double expected, void *actual_p, enum cmdline_numtype type)
+{
+ double eps;
+ double actual_d;
+
+ if (type == RTE_FLOAT_SINGLE) {
+ /* read as float, convert to double */
+ actual_d = (double)*(float *)actual_p;
+ /* FLT_EPSILON is too small for some tests */
+ eps = 1e-5f;
+ } else {
+ /* read as double */
+ actual_d = *(double *)actual_p;
+ eps = DBL_EPSILON;
+ }
+ /* compare using epsilon value */
+ if (fabs(expected - actual_d) < eps)
+ return 0;
+ /* not equal */
+ return expected < actual_d ? -1 : 1;
+}
+
+static int
+can_parse_float(double expected_result, enum cmdline_numtype type)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ if (expected_result > FLT_MAX || expected_result < -FLT_MAX)
+ return 0;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (expected_result > DBL_MAX || expected_result < -DBL_MAX)
+ return 0;
+ break;
+ default:
+ return 1;
+ }
+ return 1;
+}
+
static int
can_parse_unsigned(uint64_t expected_result, enum cmdline_numtype type)
{
@@ -386,11 +565,11 @@ test_parse_num_invalid_data(void)
int ret = 0;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result; /* pick largest buffer */
cmdline_parse_token_num_t token;
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result; /* pick largest buffer */
token.num_data.type = type;
/* test full strings */
@@ -412,6 +591,31 @@ test_parse_num_invalid_data(void)
}
}
}
+
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result; /* pick largest buffer */
+ token.num_data.type = type;
+
+ /* test full strings */
+ for (i = 0; i < RTE_DIM(float_invalid_strs); i++) {
+
+ memset(&result, 0, sizeof(double));
+ memset(&buf, 0, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*)&token,
+ float_invalid_strs[i], (void*)&result, sizeof(result));
+ if (ret != -1) {
+ /* get some info about what we are trying to parse */
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ printf("Error: parsing %s as %s succeeded!\n",
+ float_invalid_strs[i], buf);
+ return -1;
+ }
+ }
+ }
return 0;
}
@@ -423,13 +627,13 @@ test_parse_num_valid(void)
enum cmdline_numtype type;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result;
cmdline_parse_token_num_t token;
/** valid strings **/
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive strings */
@@ -468,7 +672,7 @@ test_parse_num_valid(void)
cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
buf, sizeof(buf));
- ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
num_valid_negative_strs[i].str,
(void*)&result, sizeof(result));
@@ -504,10 +708,44 @@ test_parse_num_valid(void)
}
}
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(float_valid_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ float_valid_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(float_valid_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ float_valid_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(float_valid_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ float_valid_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
/** garbage strings **/
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive garbage strings */
@@ -585,6 +823,38 @@ test_parse_num_valid(void)
}
}
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(float_garbage_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ float_garbage_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(float_garbage_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ float_garbage_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(float_garbage_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ float_garbage_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
memset(&buf, 0, sizeof(buf));
/* coverage! */
diff --git a/buildtools/dpdk-cmdline-gen.py b/buildtools/dpdk-cmdline-gen.py
index 7dadded783..6c76d7116a 100755
--- a/buildtools/dpdk-cmdline-gen.py
+++ b/buildtools/dpdk-cmdline-gen.py
@@ -17,16 +17,18 @@
RTE_SET_USED(cl);
RTE_SET_USED(data);
"""
-NUMERIC_TYPES = [
- "UINT8",
- "UINT16",
- "UINT32",
- "UINT64",
- "INT8",
- "INT16",
- "INT32",
- "INT64",
-]
+NUMERIC_TYPES = {
+ "UINT8": "uint8_t",
+ "UINT16": "uint16_t",
+ "UINT32": "uint32_t",
+ "UINT64": "uint64_t",
+ "INT8": "int8_t",
+ "INT16": "int16_t",
+ "INT32": "int32_t",
+ "INT64": "int64_t",
+ "FLOAT_SINGLE": "float",
+ "FLOAT_DOUBLE": "double",
+}
def process_command(lineno, tokens, comment):
@@ -70,7 +72,7 @@ def process_command(lineno, tokens, comment):
f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});"
)
elif t_type in NUMERIC_TYPES:
- result_struct.append(f"\t{t_type.lower()}_t {t_name};")
+ result_struct.append(f"\t{NUMERIC_TYPES[t_type]} {t_name};")
initializers.append(
f"static cmdline_parse_token_num_t cmd_{name}_{t_name}_tok =\n"
f"\tTOKEN_NUM_INITIALIZER(struct cmd_{name}_result, {t_name}, RTE_{t_type});"
diff --git a/doc/guides/prog_guide/cmdline.rst b/doc/guides/prog_guide/cmdline.rst
index e20281ceb5..447a90e32e 100644
--- a/doc/guides/prog_guide/cmdline.rst
+++ b/doc/guides/prog_guide/cmdline.rst
@@ -22,6 +22,7 @@ The DPDK command-line library supports the following features:
* Strings
* Signed/unsigned 16/32/64-bit integers
+ * Single/double precision floats
* IP Addresses
* Ethernet Addresses
@@ -68,6 +69,8 @@ The format of the list file must be:
* ``<UINT16>port_id``
+ * ``<FLOAT_SINGLE>ratio``
+
* ``<IP>src_ip``
* ``<IPv4>dst_ip4``
diff --git a/doc/guides/rel_notes/release_25_07.rst b/doc/guides/rel_notes/release_25_07.rst
index 093b85d206..54bc545110 100644
--- a/doc/guides/rel_notes/release_25_07.rst
+++ b/doc/guides/rel_notes/release_25_07.rst
@@ -55,6 +55,11 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added floating point numbers support to cmdline library.**
+
+ The cmdline library now supports parsing single- and double-precision
+ floating point numbers in interactive user commands.
+
Removed Items
-------------
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index ff2bdc28c8..887950a19d 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -8,6 +8,8 @@
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <string.h>
#include <eal_export.h>
#include <rte_string_fns.h>
@@ -34,6 +36,7 @@ struct cmdline_token_ops cmdline_token_num_ops = {
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
"INT8", "INT16", "INT32", "INT64",
+ "FLOAT_SINGLE", "FLOAT_DOUBLE"
};
static inline int
@@ -71,6 +74,14 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
if (ressize < sizeof(int64_t))
return -1;
break;
+ case RTE_FLOAT_SINGLE:
+ if (ressize < sizeof(float))
+ return -1;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (ressize < sizeof(double))
+ return -1;
+ break;
default:
debug_printf("Check size: unsupported number format: %s\n",
num_help[nd->type]);
@@ -153,6 +164,26 @@ parse_num(const char *srcbuf, uint64_t *resptr)
return end - srcbuf;
}
+static int
+parse_float(const char *srcbuf, enum cmdline_numtype type, double *resptr)
+{
+ double dres;
+ char *end;
+
+ if (type == RTE_FLOAT_SINGLE)
+ dres = (double)strtof(srcbuf, &end);
+ else if (type == RTE_FLOAT_DOUBLE)
+ dres = strtod(srcbuf, &end);
+ else
+ return -1;
+
+ if (end == srcbuf || !cmdline_isendoftoken(*end) || isinf(dres))
+ return -1;
+
+ *resptr = dres;
+ return end - srcbuf;
+}
+
static int
parse_bin(const char *srcbuf, uint64_t *res)
{
@@ -260,7 +291,25 @@ write_num(enum cmdline_numtype type, void *res, uint64_t uintres)
return 0;
}
-/* parse an int */
+static int
+write_float(enum cmdline_numtype type, void *res, double dres)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ *(float *)res = (float)dres;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ *(double *)res = dres;
+ break;
+ default:
+ debug_printf("Write failed: unsupported float format: %s\n",
+ num_help[type]);
+ return -1;
+ }
+ return 0;
+}
+
+/* parse a number */
RTE_EXPORT_SYMBOL(cmdline_parse_num)
int
cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
@@ -280,6 +329,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
if (res && check_res_size(&nd, ressize) < 0)
return -1;
+ /* integer parsing */
if (nd.type >= RTE_UINT8 && nd.type <= RTE_INT64) {
int ret, neg = *srcbuf == '-';
uint64_t uintres;
@@ -302,6 +352,21 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
return -1;
return ret;
+ /* float parsing */
+ } else if (nd.type >= RTE_FLOAT_SINGLE && nd.type <= RTE_FLOAT_DOUBLE) {
+ double dres;
+ int ret;
+
+ /* try parsing as float */
+ ret = parse_float(srcbuf, nd.type, &dres);
+ if (ret < 0)
+ return -1;
+
+ /* parsing succeeded, write the value if necessary */
+ if (res && write_float(nd.type, res, dres))
+ return -1;
+
+ return ret;
}
return -1;
}
diff --git a/lib/cmdline/cmdline_parse_num.h b/lib/cmdline/cmdline_parse_num.h
index bdd0267612..b2792a2d11 100644
--- a/lib/cmdline/cmdline_parse_num.h
+++ b/lib/cmdline/cmdline_parse_num.h
@@ -22,7 +22,9 @@ enum cmdline_numtype {
RTE_INT8,
RTE_INT16,
RTE_INT32,
- RTE_INT64
+ RTE_INT64,
+ RTE_FLOAT_SINGLE,
+ RTE_FLOAT_DOUBLE,
};
struct cmdline_token_num_data {
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v6 3/3] app/testpmd: add sleep command
2025-05-08 9:53 ` [PATCH v6 " Anatoly Burakov
2025-05-08 9:53 ` [PATCH v6 2/3] cmdline: add floating point support Anatoly Burakov
@ 2025-05-08 9:53 ` Anatoly Burakov
1 sibling, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-08 9:53 UTC (permalink / raw)
To: dev, Aman Singh
Test-pmd already has a way to run a list of commands from file, but there
is no way to pause execution for a specified amount of time between two
commands. This may be necessary for simple automation, particularly for
waiting on some asynchronous operation such as link status update.
Add a simple sleep command to wait until certain number of seconds has
passed, using the newly added cmdline library floating point support.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v1 -> v2:
- Add floating point support to cmdline
- Use floating point format for pause command
app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index d4bb3ec998..b6152c07e6 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
"quit\n"
" Quit to prompt.\n\n"
+
+ "sleep secs\n"
+ " Sleep for secs seconds (can be fractional).\n\n"
);
}
@@ -7768,6 +7771,37 @@ static cmdline_parse_inst_t cmd_quit = {
},
};
+/* *** SLEEP *** */
+struct cmd_sleep_result {
+ cmdline_fixed_string_t sleep;
+ double secs;
+};
+
+static void cmd_sleep_parsed(void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_sleep_result *res = parsed_result;
+
+ rte_delay_us_sleep(res->secs * 1E6);
+}
+
+static cmdline_parse_token_string_t cmd_sleep_sleep =
+ TOKEN_STRING_INITIALIZER(struct cmd_sleep_result, sleep, "sleep");
+static cmdline_parse_token_num_t cmd_sleep_seconds =
+ TOKEN_NUM_INITIALIZER(struct cmd_sleep_result, secs, RTE_FLOAT_DOUBLE);
+
+static cmdline_parse_inst_t cmd_sleep = {
+ .f = cmd_sleep_parsed,
+ .data = NULL,
+ .help_str = "sleep <secs>: Sleep for a specified number of seconds",
+ .tokens = {
+ (void *)&cmd_sleep_sleep,
+ (void *)&cmd_sleep_seconds,
+ NULL,
+ },
+};
+
/* *** ADD/REMOVE MAC ADDRESS FROM A PORT *** */
struct cmd_mac_addr_result {
cmdline_fixed_string_t mac_addr_cmd;
@@ -13711,6 +13745,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_showdevice,
&cmd_showcfg,
&cmd_showfwdall,
+ &cmd_sleep,
&cmd_start,
&cmd_start_tx_first,
&cmd_start_tx_first_n,
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v7 1/3] cmdline: use C standard library as number parser
2025-05-02 12:27 [PATCH v1 1/1] app/testpmd: add sleep command Anatoly Burakov
` (6 preceding siblings ...)
2025-05-08 9:53 ` [PATCH v6 " Anatoly Burakov
@ 2025-05-08 10:01 ` Anatoly Burakov
2025-05-08 10:01 ` [PATCH v7 2/3] cmdline: add floating point support Anatoly Burakov
2025-05-08 10:01 ` [PATCH v7 3/3] app/testpmd: add sleep command Anatoly Burakov
2025-05-08 13:16 ` [PATCH v8 1/3] cmdline: use C standard library as number parser Anatoly Burakov
` (2 subsequent siblings)
10 siblings, 2 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-08 10:01 UTC (permalink / raw)
To: dev
Remove custom number parser and use C standard library instead. In order to
keep compatibility with earlier versions of the parser, we have to take
into account a couple of quirks:
- We did not consider "negative" numbers to be valid for anything other
than base-10 numbers, whereas C standard library does. Adjust the tests
to match the new behavior.
- We did not consider numbers such as "+4" to be valid, whereas C
standard library does. Adjust the tests to match the new behavior.
- C standard library's strtoull does not do range checks on negative
numbers, so we have to parse knowingly-negative numbers as signed.
- C standard library does not support binary numbers, so we keep around the
relevant parts of the custom parser in place to support binary numbers.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v5 -> v6:
- Allowed more negative numbers (such as negative octals or hex)
- Updated unit tests to check new cases
- Small refactoring of code to reduce amount of noise
- More verbose debug output
v4 -> v5:
- Added this commit
app/test/test_cmdline_num.c | 19 +-
lib/cmdline/cmdline_parse_num.c | 364 ++++++++++++++++----------------
2 files changed, 195 insertions(+), 188 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index 9276de59bd..dd70f6f824 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -139,6 +139,8 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-2147483648", INT32_MIN },
{"-2147483649", INT32_MIN - 1LL },
{"-9223372036854775808", INT64_MIN },
+ {"-0x8000000000000000", INT64_MIN },
+ {"-01000000000000000000000", INT64_MIN },
};
const struct num_unsigned_str num_garbage_positive_strs[] = {
@@ -175,12 +177,27 @@ const struct num_unsigned_str num_garbage_positive_strs[] = {
const struct num_signed_str num_garbage_negative_strs[] = {
/* valid strings with garbage on the end, should still be valid */
+ /* negative numbers */
{"-9223372036854775808\0garbage", INT64_MIN },
{"-9223372036854775808\rgarbage", INT64_MIN },
{"-9223372036854775808\tgarbage", INT64_MIN },
{"-9223372036854775808\ngarbage", INT64_MIN },
{"-9223372036854775808#garbage", INT64_MIN },
{"-9223372036854775808 garbage", INT64_MIN },
+ /* negative hex */
+ {"-0x8000000000000000\0garbage", INT64_MIN },
+ {"-0x8000000000000000\rgarbage", INT64_MIN },
+ {"-0x8000000000000000\tgarbage", INT64_MIN },
+ {"-0x8000000000000000\ngarbage", INT64_MIN },
+ {"-0x8000000000000000#garbage", INT64_MIN },
+ {"-0x8000000000000000 garbage", INT64_MIN },
+ /* negative octal */
+ {"-01000000000000000000000\0garbage", INT64_MIN },
+ {"-01000000000000000000000\rgarbage", INT64_MIN },
+ {"-01000000000000000000000\tgarbage", INT64_MIN },
+ {"-01000000000000000000000\ngarbage", INT64_MIN },
+ {"-01000000000000000000000#garbage", INT64_MIN },
+ {"-01000000000000000000000 garbage", INT64_MIN },
};
const char * num_invalid_strs[] = {
@@ -197,7 +214,6 @@ const char * num_invalid_strs[] = {
"0b01110101017001",
/* false negative numbers */
"-12345F623",
- "-0x1234580A",
"-0b0111010101",
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
@@ -205,7 +221,6 @@ const char * num_invalid_strs[] = {
"1E3",
"0A",
"-B",
- "+4",
"1.23G",
"",
" ",
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index f21796bedb..ff2bdc28c8 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -4,6 +4,7 @@
* All rights reserved.
*/
+#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
@@ -29,23 +30,6 @@ struct cmdline_token_ops cmdline_token_num_ops = {
.get_help = cmdline_get_help_num,
};
-enum num_parse_state_t {
- START,
- DEC_NEG,
- BIN,
- HEX,
-
- ERROR,
-
- FIRST_OK, /* not used */
- ZERO_OK,
- HEX_OK,
- OCTAL_OK,
- BIN_OK,
- DEC_NEG_OK,
- DEC_POS_OK,
-};
-
/* Keep it sync with enum in .h */
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
@@ -53,13 +37,13 @@ static const char * num_help[] = {
};
static inline int
-add_to_res(unsigned int c, uint64_t *res, unsigned int base)
+add_to_bin(unsigned int c, uint64_t *res)
{
/* overflow */
- if ((UINT64_MAX - c) / base < *res)
+ if ((UINT64_MAX - c) / 2 < *res)
return -1;
- *res = (uint64_t) (*res * base + c);
+ *res = (uint64_t) (*res * 2 + c);
return 0;
}
@@ -88,138 +72,119 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
return -1;
break;
default:
+ debug_printf("Check size: unsupported number format: %s\n",
+ num_help[nd->type]);
return -1;
}
return 0;
}
-/* parse an int */
-RTE_EXPORT_SYMBOL(cmdline_parse_num)
-int
-cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
- unsigned ressize)
+static int
+check_parsed_num(enum cmdline_numtype type, int neg, uint64_t uintres)
{
- struct cmdline_token_num_data nd;
- enum num_parse_state_t st = START;
+ int lo_ok, hi_ok;
+
+ switch (type) {
+ case RTE_UINT8:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT8_MAX;
+ break;
+ case RTE_UINT16:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT16_MAX;
+ break;
+ case RTE_UINT32:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT32_MAX;
+ break;
+ case RTE_UINT64:
+ lo_ok = !neg;
+ hi_ok = 1; /* can't be out of range if parsed successfully */
+ break;
+ case RTE_INT8:
+ lo_ok = !neg || (int64_t)uintres >= INT8_MIN;
+ hi_ok = neg || uintres <= INT8_MAX;
+ break;
+ case RTE_INT16:
+ lo_ok = !neg || (int64_t)uintres >= INT16_MIN;
+ hi_ok = neg || uintres <= INT16_MAX;
+ break;
+ case RTE_INT32:
+ lo_ok = !neg || (int64_t)uintres >= INT32_MIN;
+ hi_ok = neg || uintres <= INT32_MAX;
+ break;
+ case RTE_INT64:
+ lo_ok = 1; /* can't be out of range if parsed successfully */
+ hi_ok = neg || uintres <= INT64_MAX;
+ break;
+ default:
+ debug_printf("Check range failed: unsupported number format: %s\n",
+ num_help[type]);
+ return -1;
+ }
+ /* check ranges */
+ if (!lo_ok || !hi_ok)
+ return -1;
+ return 0;
+}
+
+static int
+parse_num(const char *srcbuf, uint64_t *resptr)
+{
+ uint64_t uintres;
+ char *end;
+ int neg = *srcbuf == '-';
+
+ /*
+ * strtoull does not do range checks on negative numbers, so we need to
+ * use strtoll if we know the value we're parsing looks like a negative
+ * one. we use base 0 for both, 0 means autodetect base.
+ */
+ errno = 0;
+ if (neg)
+ uintres = (uint64_t)strtoll(srcbuf, &end, 0);
+ else
+ uintres = strtoull(srcbuf, &end, 0);
+
+ if (end == srcbuf || !cmdline_isendoftoken(*end) || errno == ERANGE)
+ return -1;
+
+ *resptr = uintres;
+ return end - srcbuf;
+}
+
+static int
+parse_bin(const char *srcbuf, uint64_t *res)
+{
+ uint64_t uintres = 0;
+ enum {
+ ERROR,
+ START,
+ BIN,
+ ZERO_OK,
+ BIN_OK,
+ } st = START;
const char * buf;
char c;
- uint64_t res1 = 0;
-
- if (!tk)
- return -1;
-
- if (!srcbuf || !*srcbuf)
- return -1;
buf = srcbuf;
c = *buf;
-
- memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
-
- /* check that we have enough room in res */
- if (res) {
- if (check_res_size(&nd, ressize) < 0)
- return -1;
- }
-
while (st != ERROR && c && !cmdline_isendoftoken(c)) {
debug_printf("%c %x -> ", c, c);
switch (st) {
case START:
- if (c == '-') {
- st = DEC_NEG;
- }
- else if (c == '0') {
+ if (c == '0') {
st = ZERO_OK;
}
- else if (c >= '1' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = DEC_POS_OK;
- }
- else {
+ else {
st = ERROR;
}
break;
case ZERO_OK:
- if (c == 'x') {
- st = HEX;
- }
- else if (c == 'b') {
+ if (c == 'b') {
st = BIN;
}
- else if (c >= '0' && c <= '7') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = OCTAL_OK;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_NEG:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = DEC_NEG_OK;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_NEG_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_POS_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
- case HEX:
- st = HEX_OK;
- /* fall-through */
- case HEX_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 16) < 0)
- st = ERROR;
- }
- else if (c >= 'a' && c <= 'f') {
- if (add_to_res(c - 'a' + 10, &res1, 16) < 0)
- st = ERROR;
- }
- else if (c >= 'A' && c <= 'F') {
- if (add_to_res(c - 'A' + 10, &res1, 16) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
-
- case OCTAL_OK:
- if (c >= '0' && c <= '7') {
- if (add_to_res(c - '0', &res1, 8) < 0)
- st = ERROR;
- }
else {
st = ERROR;
}
@@ -230,7 +195,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
/* fall-through */
case BIN_OK:
if (c >= '0' && c <= '1') {
- if (add_to_res(c - '0', &res1, 2) < 0)
+ if (add_to_bin(c - '0', &uintres) < 0)
st = ERROR;
}
else {
@@ -239,10 +204,10 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
break;
default:
debug_printf("not impl ");
-
+ st = ERROR;
}
- debug_printf("(%"PRIu64")\n", res1);
+ debug_printf("(%"PRIu64")\n", uintres);
buf ++;
c = *buf;
@@ -252,66 +217,93 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
return -1;
}
- switch (st) {
- case ZERO_OK:
- case DEC_POS_OK:
- case HEX_OK:
- case OCTAL_OK:
- case BIN_OK:
- if (nd.type == RTE_INT8 && res1 <= INT8_MAX) {
- if (res) *(int8_t *)res = (int8_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT16 && res1 <= INT16_MAX) {
- if (res) *(int16_t *)res = (int16_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT32 && res1 <= INT32_MAX) {
- if (res) *(int32_t *)res = (int32_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT64 && res1 <= INT64_MAX) {
- if (res) *(int64_t *)res = (int64_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT8 && res1 <= UINT8_MAX) {
- if (res) *(uint8_t *)res = (uint8_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT16 && res1 <= UINT16_MAX) {
- if (res) *(uint16_t *)res = (uint16_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT32 && res1 <= UINT32_MAX) {
- if (res) *(uint32_t *)res = (uint32_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT64) {
- if (res) *(uint64_t *)res = res1;
- return buf-srcbuf;
- } else {
- return -1;
- }
- break;
+ if (st != BIN_OK)
+ return -1;
+
+ *res = uintres;
+ return buf - srcbuf;
+}
- case DEC_NEG_OK:
- if (nd.type == RTE_INT8 &&
- res1 <= INT8_MAX + 1) {
- if (res) *(int8_t *)res = (int8_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT16 &&
- res1 <= (uint16_t)INT16_MAX + 1) {
- if (res) *(int16_t *)res = (int16_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT32 &&
- res1 <= (uint32_t)INT32_MAX + 1) {
- if (res) *(int32_t *)res = (int32_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT64 &&
- res1 <= (uint64_t)INT64_MAX + 1) {
- if (res) *(int64_t *)res = (int64_t) (-res1);
- return buf-srcbuf;
- } else {
- return -1;
- }
+static int
+write_num(enum cmdline_numtype type, void *res, uint64_t uintres)
+{
+ switch (type) {
+ case RTE_UINT8:
+ *(uint8_t *)res = (uint8_t)uintres;
+ break;
+ case RTE_UINT16:
+ *(uint16_t *)res = (uint16_t)uintres;
+ break;
+ case RTE_UINT32:
+ *(uint32_t *)res = (uint32_t)uintres;
+ break;
+ case RTE_UINT64:
+ *(uint64_t *)res = uintres;
+ break;
+ case RTE_INT8:
+ *(int8_t *)res = (int8_t)uintres;
+ break;
+ case RTE_INT16:
+ *(int16_t *)res = (int16_t)uintres;
+ break;
+ case RTE_INT32:
+ *(int32_t *)res = (int32_t)uintres;
+ break;
+ case RTE_INT64:
+ *(int64_t *)res = (int64_t)uintres;
break;
default:
- debug_printf("error\n");
+ debug_printf("Write failed: unsupported number format: %s\n",
+ num_help[type]);
return -1;
}
+ return 0;
+}
+
+/* parse an int */
+RTE_EXPORT_SYMBOL(cmdline_parse_num)
+int
+cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
+ unsigned ressize)
+{
+ struct cmdline_token_num_data nd;
+
+ if (!tk)
+ return -1;
+
+ if (!srcbuf || !*srcbuf)
+ return -1;
+
+ memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
+
+ /* check that we have enough room in res */
+ if (res && check_res_size(&nd, ressize) < 0)
+ return -1;
+
+ if (nd.type >= RTE_UINT8 && nd.type <= RTE_INT64) {
+ int ret, neg = *srcbuf == '-';
+ uint64_t uintres;
+
+ /* try parsing as number */
+ ret = parse_num(srcbuf, &uintres);
+
+ if (ret < 0) {
+ /* parse failed, try parsing as binary */
+ ret = parse_bin(srcbuf, &uintres);
+ if (ret < 0)
+ return -1;
+ }
+ /* check if we're within valid range */
+ if (check_parsed_num(nd.type, neg, uintres) < 0)
+ return -1;
+
+ /* parsing succeeded, write the value if necessary */
+ if (res && write_num(nd.type, res, uintres) < 0)
+ return -1;
+
+ return ret;
+ }
+ return -1;
}
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v7 2/3] cmdline: add floating point support
2025-05-08 10:01 ` [PATCH v7 1/3] cmdline: use C standard library as number parser Anatoly Burakov
@ 2025-05-08 10:01 ` Anatoly Burakov
2025-05-08 10:09 ` Burakov, Anatoly
2025-05-08 10:01 ` [PATCH v7 3/3] app/testpmd: add sleep command Anatoly Burakov
1 sibling, 1 reply; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-08 10:01 UTC (permalink / raw)
To: dev
Add support for parsing floating point numbers in cmdline library, as well
as unit tests for the new functionality. Use C library for parsing.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v5 -> v6:
- Small refactor to reduce amount of noise
- Use strtof for parsing single precision floats
- More unit tests
v4 -> v5:
- Reworked to use standard C library functions as much as possible,
keeping near-100% compatibility with earlier versions (the only
difference is that strings like "+4" are now considered valid)
v3 -> v4:
- Removed unnecessary check for integer overflow when parsing negative
floats (as we convert to double before changing sign)
- Make naming of float exponent states more consistent
v2 -> v3:
- Fixed a bug where a free-standing negative exponent ("1e-") would attempt to be
parsed, and added unit tests for this case
- Added support for floats in dpdk-cmdline-gen script
- Added documentation updates to call out float support
app/test/test_cmdline_num.c | 284 ++++++++++++++++++++++++-
buildtools/dpdk-cmdline-gen.py | 24 ++-
doc/guides/prog_guide/cmdline.rst | 3 +
doc/guides/rel_notes/release_25_07.rst | 5 +
lib/cmdline/cmdline_parse_num.c | 67 +++++-
lib/cmdline/cmdline_parse_num.h | 4 +-
6 files changed, 368 insertions(+), 19 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index dd70f6f824..97028d6d82 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -5,6 +5,8 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <rte_string_fns.h>
@@ -23,6 +25,11 @@ struct num_signed_str {
int64_t result;
};
+struct num_float_str {
+ const char * str;
+ double result;
+};
+
const struct num_unsigned_str num_valid_positive_strs[] = {
/* decimal positive */
{"0", 0 },
@@ -143,6 +150,63 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-01000000000000000000000", INT64_MIN },
};
+const struct num_float_str float_valid_strs[] = {
+ /* zero */
+ {"0", 0},
+ /* parse int as float */
+ {"1", 1},
+ {"-1", -1},
+ /* fractional */
+ {"1.23", 1.23},
+ {"-1.23", -1.23},
+ {"0.123", 0.123},
+ {"-0.123", -0.123},
+ {"123.456", 123.456},
+ {"-123.456", -123.456},
+ /* positive exponent */
+ {"1e2", 1e2},
+ {"-1e2", -1e2},
+ {"1E2", 1E2},
+ {"-1E2", -1E2},
+ {"0.12e3", 0.12e3},
+ {"-0.12e3", -0.12e3},
+ {"1.23e4", 1.23e4},
+ {"-1.23e4", -1.23e4},
+ {"1.23E4", 1.23E4},
+ {"-1.23E4", -1.23E4},
+ {"123.456e7", 123.456e7},
+ {"-123.456e7", -123.456e7},
+ {"123.456E7", 123.456E7},
+ {"-123.456E7", -123.456E7},
+ /* negative exponent */
+ {"1e-2", 1e-2},
+ {"-1e-2", -1e-2},
+ {"1E-2", 1E-2},
+ {"-1E-2", -1E-2},
+ {"0.12e-3", 0.12e-3},
+ {"-0.12e-3", -0.12e-3},
+ {"1.23e-4", 1.23e-4},
+ {"-1.23e-4", -1.23e-4},
+ {"1.23E-4", 1.23E-4},
+ {"-1.23E-4", -1.23E-4},
+ {"123.456e-7", 123.456e-7},
+ {"-123.456e-7", -123.456e-7},
+ {"123.456E-7", 123.456E-7},
+ {"-123.456E-7", -123.456E-7},
+ /* try overflowing float */
+ {"2e63", 2e63},
+ {"-2e63", -2e63},
+ {"2E63", 2E63},
+ {"-2E63", -2E63},
+ {"18446744073709551615", (double) UINT64_MAX},
+ {"-9223372036854775808", (double) INT64_MIN},
+ /* try overflowing double */
+ {"2e308", HUGE_VAL},
+ {"-2e308", -HUGE_VAL},
+ {"2E308", HUGE_VAL},
+ {"-2E308", HUGE_VAL},
+};
+
const struct num_unsigned_str num_garbage_positive_strs[] = {
/* valid strings with garbage on the end, should still be valid */
/* decimal */
@@ -200,6 +264,71 @@ const struct num_signed_str num_garbage_negative_strs[] = {
{"-01000000000000000000000 garbage", INT64_MIN },
};
+const struct num_float_str float_garbage_strs[] = {
+ /* valid strings with garbage on the end, should still be valid */
+ /* positive float positive exponent */
+ {"123.456e7\0garbage", 123.456e7},
+ {"123.456e7\rgarbage", 123.456e7},
+ {"123.456e7\tgarbage", 123.456e7},
+ {"123.456e7\ngarbage", 123.456e7},
+ {"123.456e7#garbage", 123.456e7},
+ {"123.456e7 garbage", 123.456e7},
+ /* negative float positive exponent */
+ {"-123.456e7\0garbage", -123.456e7},
+ {"-123.456e7\rgarbage", -123.456e7},
+ {"-123.456e7\tgarbage", -123.456e7},
+ {"-123.456e7\ngarbage", -123.456e7},
+ {"-123.456e7#garbage", -123.456e7},
+ {"-123.456e7 garbage", -123.456e7},
+ /* positive float negative exponent */
+ {"123.456e-7\0garbage", 123.456e-7},
+ {"123.456e-7\rgarbage", 123.456e-7},
+ {"123.456e-7\tgarbage", 123.456e-7},
+ {"123.456e-7\ngarbage", 123.456e-7},
+ {"123.456e-7#garbage", 123.456e-7},
+ {"123.456e-7 garbage", 123.456e-7},
+ /* negative float negative exponent */
+ {"-123.456e-7\0garbage", -123.456e-7},
+ {"-123.456e-7\rgarbage", -123.456e-7},
+ {"-123.456e-7\tgarbage", -123.456e-7},
+ {"-123.456e-7\ngarbage", -123.456e-7},
+ {"-123.456e-7#garbage", -123.456e-7},
+ {"-123.456e-7 garbage", -123.456e-7},
+ /* float overflows */
+ {"18446744073709551615\0garbage", (double) UINT64_MAX},
+ {"18446744073709551615\rgarbage", (double) UINT64_MAX},
+ {"18446744073709551615\ngarbage", (double) UINT64_MAX},
+ {"18446744073709551615\tgarbage", (double) UINT64_MAX},
+ {"18446744073709551615#garbage", (double) UINT64_MAX},
+ {"18446744073709551615 garbage", (double) UINT64_MAX},
+ {"-9223372036854775808\0garbage", (double) INT64_MIN},
+ {"-9223372036854775808\rgarbage", (double) INT64_MIN},
+ {"-9223372036854775808\ngarbage", (double) INT64_MIN},
+ {"-9223372036854775808\tgarbage", (double) INT64_MIN},
+ {"-9223372036854775808#garbage", (double) INT64_MIN},
+ {"-9223372036854775808 garbage", (double) INT64_MIN},
+};
+
+const char *float_invalid_strs[] = {
+ "1.1.",
+ "1.1.1",
+ "-1.1.",
+ "-1.1.1",
+ "e",
+ "1e",
+ "-1e",
+ "0.1e",
+ "-0.1e",
+ "1.e",
+ "-1.e",
+ "1.23e3.4",
+ "-1.23e3.4",
+ "1e1e",
+ "1e1e1",
+ "1e-",
+ "-1e-"
+};
+
const char * num_invalid_strs[] = {
"18446744073709551616", /* out of range unsigned */
"-9223372036854775809", /* out of range negative signed */
@@ -218,7 +347,16 @@ const char * num_invalid_strs[] = {
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
"1111000011110000111100001111000011110000111100001111000011110000"),
+ /* valid float values but should fail to parse as ints */
"1E3",
+ "-1E3",
+ "1.23",
+ "-1.23",
+ "1E-3",
+ "-1E-3",
+ "1.23E4",
+ "-1.23E4",
+ /* misc invalid values */
"0A",
"-B",
"1.23G",
@@ -231,6 +369,49 @@ const char * num_invalid_strs[] = {
"\0",
};
+static int
+float_cmp(double expected, void *actual_p, enum cmdline_numtype type)
+{
+ double eps;
+ double actual_d;
+
+ if (type == RTE_FLOAT_SINGLE) {
+ /* read as float, convert to double */
+ actual_d = (double)*(float *)actual_p;
+ /* downcast expected to float as well */
+ expected = (double)(float)expected;
+ /* FLT_EPSILON is too small for some tests */
+ eps = FLT_EPSILON;
+ } else {
+ /* read as double */
+ actual_d = *(double *)actual_p;
+ eps = DBL_EPSILON;
+ }
+ /* compare using epsilon value */
+ if (fabs(expected - actual_d) < eps)
+ return 0;
+ /* not equal */
+ return expected < actual_d ? -1 : 1;
+}
+
+static int
+can_parse_float(double expected_result, enum cmdline_numtype type)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ if (expected_result > FLT_MAX || expected_result < -FLT_MAX)
+ return 0;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (expected_result > DBL_MAX || expected_result < -DBL_MAX)
+ return 0;
+ break;
+ default:
+ return 1;
+ }
+ return 1;
+}
+
static int
can_parse_unsigned(uint64_t expected_result, enum cmdline_numtype type)
{
@@ -386,11 +567,11 @@ test_parse_num_invalid_data(void)
int ret = 0;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result; /* pick largest buffer */
cmdline_parse_token_num_t token;
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result; /* pick largest buffer */
token.num_data.type = type;
/* test full strings */
@@ -412,6 +593,31 @@ test_parse_num_invalid_data(void)
}
}
}
+
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result; /* pick largest buffer */
+ token.num_data.type = type;
+
+ /* test full strings */
+ for (i = 0; i < RTE_DIM(float_invalid_strs); i++) {
+
+ memset(&result, 0, sizeof(double));
+ memset(&buf, 0, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*)&token,
+ float_invalid_strs[i], (void*)&result, sizeof(result));
+ if (ret != -1) {
+ /* get some info about what we are trying to parse */
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ printf("Error: parsing %s as %s succeeded!\n",
+ float_invalid_strs[i], buf);
+ return -1;
+ }
+ }
+ }
return 0;
}
@@ -423,13 +629,13 @@ test_parse_num_valid(void)
enum cmdline_numtype type;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result;
cmdline_parse_token_num_t token;
/** valid strings **/
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive strings */
@@ -468,7 +674,7 @@ test_parse_num_valid(void)
cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
buf, sizeof(buf));
- ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
num_valid_negative_strs[i].str,
(void*)&result, sizeof(result));
@@ -504,10 +710,44 @@ test_parse_num_valid(void)
}
}
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(float_valid_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ float_valid_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(float_valid_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ float_valid_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(float_valid_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ float_valid_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
/** garbage strings **/
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive garbage strings */
@@ -585,6 +825,38 @@ test_parse_num_valid(void)
}
}
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(float_garbage_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ float_garbage_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(float_garbage_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ float_garbage_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(float_garbage_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ float_garbage_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
memset(&buf, 0, sizeof(buf));
/* coverage! */
diff --git a/buildtools/dpdk-cmdline-gen.py b/buildtools/dpdk-cmdline-gen.py
index 7dadded783..6c76d7116a 100755
--- a/buildtools/dpdk-cmdline-gen.py
+++ b/buildtools/dpdk-cmdline-gen.py
@@ -17,16 +17,18 @@
RTE_SET_USED(cl);
RTE_SET_USED(data);
"""
-NUMERIC_TYPES = [
- "UINT8",
- "UINT16",
- "UINT32",
- "UINT64",
- "INT8",
- "INT16",
- "INT32",
- "INT64",
-]
+NUMERIC_TYPES = {
+ "UINT8": "uint8_t",
+ "UINT16": "uint16_t",
+ "UINT32": "uint32_t",
+ "UINT64": "uint64_t",
+ "INT8": "int8_t",
+ "INT16": "int16_t",
+ "INT32": "int32_t",
+ "INT64": "int64_t",
+ "FLOAT_SINGLE": "float",
+ "FLOAT_DOUBLE": "double",
+}
def process_command(lineno, tokens, comment):
@@ -70,7 +72,7 @@ def process_command(lineno, tokens, comment):
f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});"
)
elif t_type in NUMERIC_TYPES:
- result_struct.append(f"\t{t_type.lower()}_t {t_name};")
+ result_struct.append(f"\t{NUMERIC_TYPES[t_type]} {t_name};")
initializers.append(
f"static cmdline_parse_token_num_t cmd_{name}_{t_name}_tok =\n"
f"\tTOKEN_NUM_INITIALIZER(struct cmd_{name}_result, {t_name}, RTE_{t_type});"
diff --git a/doc/guides/prog_guide/cmdline.rst b/doc/guides/prog_guide/cmdline.rst
index e20281ceb5..447a90e32e 100644
--- a/doc/guides/prog_guide/cmdline.rst
+++ b/doc/guides/prog_guide/cmdline.rst
@@ -22,6 +22,7 @@ The DPDK command-line library supports the following features:
* Strings
* Signed/unsigned 16/32/64-bit integers
+ * Single/double precision floats
* IP Addresses
* Ethernet Addresses
@@ -68,6 +69,8 @@ The format of the list file must be:
* ``<UINT16>port_id``
+ * ``<FLOAT_SINGLE>ratio``
+
* ``<IP>src_ip``
* ``<IPv4>dst_ip4``
diff --git a/doc/guides/rel_notes/release_25_07.rst b/doc/guides/rel_notes/release_25_07.rst
index 093b85d206..54bc545110 100644
--- a/doc/guides/rel_notes/release_25_07.rst
+++ b/doc/guides/rel_notes/release_25_07.rst
@@ -55,6 +55,11 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added floating point numbers support to cmdline library.**
+
+ The cmdline library now supports parsing single- and double-precision
+ floating point numbers in interactive user commands.
+
Removed Items
-------------
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index ff2bdc28c8..887950a19d 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -8,6 +8,8 @@
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <string.h>
#include <eal_export.h>
#include <rte_string_fns.h>
@@ -34,6 +36,7 @@ struct cmdline_token_ops cmdline_token_num_ops = {
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
"INT8", "INT16", "INT32", "INT64",
+ "FLOAT_SINGLE", "FLOAT_DOUBLE"
};
static inline int
@@ -71,6 +74,14 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
if (ressize < sizeof(int64_t))
return -1;
break;
+ case RTE_FLOAT_SINGLE:
+ if (ressize < sizeof(float))
+ return -1;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (ressize < sizeof(double))
+ return -1;
+ break;
default:
debug_printf("Check size: unsupported number format: %s\n",
num_help[nd->type]);
@@ -153,6 +164,26 @@ parse_num(const char *srcbuf, uint64_t *resptr)
return end - srcbuf;
}
+static int
+parse_float(const char *srcbuf, enum cmdline_numtype type, double *resptr)
+{
+ double dres;
+ char *end;
+
+ if (type == RTE_FLOAT_SINGLE)
+ dres = (double)strtof(srcbuf, &end);
+ else if (type == RTE_FLOAT_DOUBLE)
+ dres = strtod(srcbuf, &end);
+ else
+ return -1;
+
+ if (end == srcbuf || !cmdline_isendoftoken(*end) || isinf(dres))
+ return -1;
+
+ *resptr = dres;
+ return end - srcbuf;
+}
+
static int
parse_bin(const char *srcbuf, uint64_t *res)
{
@@ -260,7 +291,25 @@ write_num(enum cmdline_numtype type, void *res, uint64_t uintres)
return 0;
}
-/* parse an int */
+static int
+write_float(enum cmdline_numtype type, void *res, double dres)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ *(float *)res = (float)dres;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ *(double *)res = dres;
+ break;
+ default:
+ debug_printf("Write failed: unsupported float format: %s\n",
+ num_help[type]);
+ return -1;
+ }
+ return 0;
+}
+
+/* parse a number */
RTE_EXPORT_SYMBOL(cmdline_parse_num)
int
cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
@@ -280,6 +329,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
if (res && check_res_size(&nd, ressize) < 0)
return -1;
+ /* integer parsing */
if (nd.type >= RTE_UINT8 && nd.type <= RTE_INT64) {
int ret, neg = *srcbuf == '-';
uint64_t uintres;
@@ -302,6 +352,21 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
return -1;
return ret;
+ /* float parsing */
+ } else if (nd.type >= RTE_FLOAT_SINGLE && nd.type <= RTE_FLOAT_DOUBLE) {
+ double dres;
+ int ret;
+
+ /* try parsing as float */
+ ret = parse_float(srcbuf, nd.type, &dres);
+ if (ret < 0)
+ return -1;
+
+ /* parsing succeeded, write the value if necessary */
+ if (res && write_float(nd.type, res, dres))
+ return -1;
+
+ return ret;
}
return -1;
}
diff --git a/lib/cmdline/cmdline_parse_num.h b/lib/cmdline/cmdline_parse_num.h
index bdd0267612..b2792a2d11 100644
--- a/lib/cmdline/cmdline_parse_num.h
+++ b/lib/cmdline/cmdline_parse_num.h
@@ -22,7 +22,9 @@ enum cmdline_numtype {
RTE_INT8,
RTE_INT16,
RTE_INT32,
- RTE_INT64
+ RTE_INT64,
+ RTE_FLOAT_SINGLE,
+ RTE_FLOAT_DOUBLE,
};
struct cmdline_token_num_data {
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v7 3/3] app/testpmd: add sleep command
2025-05-08 10:01 ` [PATCH v7 1/3] cmdline: use C standard library as number parser Anatoly Burakov
2025-05-08 10:01 ` [PATCH v7 2/3] cmdline: add floating point support Anatoly Burakov
@ 2025-05-08 10:01 ` Anatoly Burakov
1 sibling, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-08 10:01 UTC (permalink / raw)
To: dev, Aman Singh
Test-pmd already has a way to run a list of commands from file, but there
is no way to pause execution for a specified amount of time between two
commands. This may be necessary for simple automation, particularly for
waiting on some asynchronous operation such as link status update.
Add a simple sleep command to wait until certain number of seconds has
passed, using the newly added cmdline library floating point support.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v1 -> v2:
- Add floating point support to cmdline
- Use floating point format for pause command
app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index d4bb3ec998..b6152c07e6 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
"quit\n"
" Quit to prompt.\n\n"
+
+ "sleep secs\n"
+ " Sleep for secs seconds (can be fractional).\n\n"
);
}
@@ -7768,6 +7771,37 @@ static cmdline_parse_inst_t cmd_quit = {
},
};
+/* *** SLEEP *** */
+struct cmd_sleep_result {
+ cmdline_fixed_string_t sleep;
+ double secs;
+};
+
+static void cmd_sleep_parsed(void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_sleep_result *res = parsed_result;
+
+ rte_delay_us_sleep(res->secs * 1E6);
+}
+
+static cmdline_parse_token_string_t cmd_sleep_sleep =
+ TOKEN_STRING_INITIALIZER(struct cmd_sleep_result, sleep, "sleep");
+static cmdline_parse_token_num_t cmd_sleep_seconds =
+ TOKEN_NUM_INITIALIZER(struct cmd_sleep_result, secs, RTE_FLOAT_DOUBLE);
+
+static cmdline_parse_inst_t cmd_sleep = {
+ .f = cmd_sleep_parsed,
+ .data = NULL,
+ .help_str = "sleep <secs>: Sleep for a specified number of seconds",
+ .tokens = {
+ (void *)&cmd_sleep_sleep,
+ (void *)&cmd_sleep_seconds,
+ NULL,
+ },
+};
+
/* *** ADD/REMOVE MAC ADDRESS FROM A PORT *** */
struct cmd_mac_addr_result {
cmdline_fixed_string_t mac_addr_cmd;
@@ -13711,6 +13745,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_showdevice,
&cmd_showcfg,
&cmd_showfwdall,
+ &cmd_sleep,
&cmd_start,
&cmd_start_tx_first,
&cmd_start_tx_first_n,
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v7 2/3] cmdline: add floating point support
2025-05-08 10:01 ` [PATCH v7 2/3] cmdline: add floating point support Anatoly Burakov
@ 2025-05-08 10:09 ` Burakov, Anatoly
0 siblings, 0 replies; 45+ messages in thread
From: Burakov, Anatoly @ 2025-05-08 10:09 UTC (permalink / raw)
To: dev
On 5/8/2025 12:01 PM, Anatoly Burakov wrote:
> Add support for parsing floating point numbers in cmdline library, as well
> as unit tests for the new functionality. Use C library for parsing.
>
> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> ---
>
> Notes:
v6 -> v7:
- Fixed a bug in float compare in unit tests where a bigger epsilon
value than necessary was "needed" because we were comparing float
result to a double result
> v5 -> v6:
> - Small refactor to reduce amount of noise
> - Use strtof for parsing single precision floats
> - More unit tests
>
> v4 -> v5:
> - Reworked to use standard C library functions as much as possible,
> keeping near-100% compatibility with earlier versions (the only
> difference is that strings like "+4" are now considered valid)
>
> v3 -> v4:
> - Removed unnecessary check for integer overflow when parsing negative
> floats (as we convert to double before changing sign)
> - Make naming of float exponent states more consistent
>
> v2 -> v3:
> - Fixed a bug where a free-standing negative exponent ("1e-") would attempt to be
> parsed, and added unit tests for this case
> - Added support for floats in dpdk-cmdline-gen script
> - Added documentation updates to call out float support
>
> app/test/test_cmdline_num.c | 284 ++++++++++++++++++++++++-
> buildtools/dpdk-cmdline-gen.py | 24 ++-
> doc/guides/prog_guide/cmdline.rst | 3 +
> doc/guides/rel_notes/release_25_07.rst | 5 +
> lib/cmdline/cmdline_parse_num.c | 67 +++++-
> lib/cmdline/cmdline_parse_num.h | 4 +-
> 6 files changed, 368 insertions(+), 19 deletions(-)
>
...but forgot to remove the comment:
> +static int
> +float_cmp(double expected, void *actual_p, enum cmdline_numtype type)
> +{
> + double eps;
> + double actual_d;
> +
> + if (type == RTE_FLOAT_SINGLE) {
> + /* read as float, convert to double */
> + actual_d = (double)*(float *)actual_p;
> + /* downcast expected to float as well */
> + expected = (double)(float)expected;
> + /* FLT_EPSILON is too small for some tests */
Please fix this on apply if there is no v8, thanks :)
> + eps = FLT_EPSILON;
> + } else {
> + /* read as double */
> + actual_d = *(double *)actual_p;
> + eps = DBL_EPSILON;
> + }
> + /* compare using epsilon value */
> + if (fabs(expected - actual_d) < eps)
> + return 0;
> + /* not equal */
> + return expected < actual_d ? -1 : 1;
> +}
> +
--
Thanks,
Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v8 1/3] cmdline: use C standard library as number parser
2025-05-02 12:27 [PATCH v1 1/1] app/testpmd: add sleep command Anatoly Burakov
` (7 preceding siblings ...)
2025-05-08 10:01 ` [PATCH v7 1/3] cmdline: use C standard library as number parser Anatoly Burakov
@ 2025-05-08 13:16 ` Anatoly Burakov
2025-05-08 13:16 ` [PATCH v8 2/3] cmdline: add floating point support Anatoly Burakov
` (2 more replies)
2025-05-09 13:39 ` [PATCH v9 " Anatoly Burakov
2025-05-09 14:41 ` [PATCH v10 " Anatoly Burakov
10 siblings, 3 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-08 13:16 UTC (permalink / raw)
To: dev
Remove custom number parser and use C standard library instead. In order to
keep compatibility with earlier versions of the parser, we have to take
into account a couple of quirks:
- We did not consider "negative" numbers to be valid for anything other
than base-10 numbers, whereas C standard library does. Adjust the tests
to match the new behavior.
- We did not consider numbers such as "+4" to be valid, whereas C
standard library does. Adjust the tests to match the new behavior.
- C standard library's strtoull does not do range checks on negative
numbers, so we have to parse knowingly-negative numbers as signed.
- C standard library does not support binary numbers, so we keep around the
relevant parts of the custom parser in place to support binary numbers.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v7 -> v8:
- Added the commented-out out-of-bounds check back
- Replaced debug print messages to ensure they don't attempt to
index the num_help[] array (should fix compile errors)
v5 -> v6:
- Allowed more negative numbers (such as negative octals or hex)
- Updated unit tests to check new cases
- Small refactoring of code to reduce amount of noise
- More verbose debug output
v4 -> v5:
- Added this commit
app/test/test_cmdline_num.c | 19 +-
lib/cmdline/cmdline_parse_num.c | 367 ++++++++++++++++----------------
2 files changed, 195 insertions(+), 191 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index 9276de59bd..dd70f6f824 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -139,6 +139,8 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-2147483648", INT32_MIN },
{"-2147483649", INT32_MIN - 1LL },
{"-9223372036854775808", INT64_MIN },
+ {"-0x8000000000000000", INT64_MIN },
+ {"-01000000000000000000000", INT64_MIN },
};
const struct num_unsigned_str num_garbage_positive_strs[] = {
@@ -175,12 +177,27 @@ const struct num_unsigned_str num_garbage_positive_strs[] = {
const struct num_signed_str num_garbage_negative_strs[] = {
/* valid strings with garbage on the end, should still be valid */
+ /* negative numbers */
{"-9223372036854775808\0garbage", INT64_MIN },
{"-9223372036854775808\rgarbage", INT64_MIN },
{"-9223372036854775808\tgarbage", INT64_MIN },
{"-9223372036854775808\ngarbage", INT64_MIN },
{"-9223372036854775808#garbage", INT64_MIN },
{"-9223372036854775808 garbage", INT64_MIN },
+ /* negative hex */
+ {"-0x8000000000000000\0garbage", INT64_MIN },
+ {"-0x8000000000000000\rgarbage", INT64_MIN },
+ {"-0x8000000000000000\tgarbage", INT64_MIN },
+ {"-0x8000000000000000\ngarbage", INT64_MIN },
+ {"-0x8000000000000000#garbage", INT64_MIN },
+ {"-0x8000000000000000 garbage", INT64_MIN },
+ /* negative octal */
+ {"-01000000000000000000000\0garbage", INT64_MIN },
+ {"-01000000000000000000000\rgarbage", INT64_MIN },
+ {"-01000000000000000000000\tgarbage", INT64_MIN },
+ {"-01000000000000000000000\ngarbage", INT64_MIN },
+ {"-01000000000000000000000#garbage", INT64_MIN },
+ {"-01000000000000000000000 garbage", INT64_MIN },
};
const char * num_invalid_strs[] = {
@@ -197,7 +214,6 @@ const char * num_invalid_strs[] = {
"0b01110101017001",
/* false negative numbers */
"-12345F623",
- "-0x1234580A",
"-0b0111010101",
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
@@ -205,7 +221,6 @@ const char * num_invalid_strs[] = {
"1E3",
"0A",
"-B",
- "+4",
"1.23G",
"",
" ",
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index f21796bedb..1e04a59038 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -4,6 +4,7 @@
* All rights reserved.
*/
+#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
@@ -29,23 +30,6 @@ struct cmdline_token_ops cmdline_token_num_ops = {
.get_help = cmdline_get_help_num,
};
-enum num_parse_state_t {
- START,
- DEC_NEG,
- BIN,
- HEX,
-
- ERROR,
-
- FIRST_OK, /* not used */
- ZERO_OK,
- HEX_OK,
- OCTAL_OK,
- BIN_OK,
- DEC_NEG_OK,
- DEC_POS_OK,
-};
-
/* Keep it sync with enum in .h */
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
@@ -53,13 +37,13 @@ static const char * num_help[] = {
};
static inline int
-add_to_res(unsigned int c, uint64_t *res, unsigned int base)
+add_to_bin(unsigned int c, uint64_t *res)
{
/* overflow */
- if ((UINT64_MAX - c) / base < *res)
+ if ((UINT64_MAX - c) / 2 < *res)
return -1;
- *res = (uint64_t) (*res * base + c);
+ *res = (uint64_t) (*res * 2 + c);
return 0;
}
@@ -88,138 +72,117 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
return -1;
break;
default:
+ debug_printf("Wrong number type: %d\n", nd->type);
return -1;
}
return 0;
}
-/* parse an int */
-RTE_EXPORT_SYMBOL(cmdline_parse_num)
-int
-cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
- unsigned ressize)
+static int
+check_parsed_num(enum cmdline_numtype type, int neg, uint64_t uintres)
{
- struct cmdline_token_num_data nd;
- enum num_parse_state_t st = START;
+ int lo_ok, hi_ok;
+
+ switch (type) {
+ case RTE_UINT8:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT8_MAX;
+ break;
+ case RTE_UINT16:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT16_MAX;
+ break;
+ case RTE_UINT32:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT32_MAX;
+ break;
+ case RTE_UINT64:
+ lo_ok = !neg;
+ hi_ok = 1; /* can't be out of range if parsed successfully */
+ break;
+ case RTE_INT8:
+ lo_ok = !neg || (int64_t)uintres >= INT8_MIN;
+ hi_ok = neg || uintres <= INT8_MAX;
+ break;
+ case RTE_INT16:
+ lo_ok = !neg || (int64_t)uintres >= INT16_MIN;
+ hi_ok = neg || uintres <= INT16_MAX;
+ break;
+ case RTE_INT32:
+ lo_ok = !neg || (int64_t)uintres >= INT32_MIN;
+ hi_ok = neg || uintres <= INT32_MAX;
+ break;
+ case RTE_INT64:
+ lo_ok = 1; /* can't be out of range if parsed successfully */
+ hi_ok = neg || uintres <= INT64_MAX;
+ break;
+ default:
+ debug_printf("Wrong number type\n");
+ return -1;
+ }
+ /* check ranges */
+ if (!lo_ok || !hi_ok)
+ return -1;
+ return 0;
+}
+
+static int
+parse_num(const char *srcbuf, uint64_t *resptr)
+{
+ uint64_t uintres;
+ char *end;
+ int neg = *srcbuf == '-';
+
+ /*
+ * strtoull does not do range checks on negative numbers, so we need to
+ * use strtoll if we know the value we're parsing looks like a negative
+ * one. we use base 0 for both, 0 means autodetect base.
+ */
+ errno = 0;
+ if (neg)
+ uintres = (uint64_t)strtoll(srcbuf, &end, 0);
+ else
+ uintres = strtoull(srcbuf, &end, 0);
+
+ if (end == srcbuf || !cmdline_isendoftoken(*end) || errno == ERANGE)
+ return -1;
+
+ *resptr = uintres;
+ return end - srcbuf;
+}
+
+static int
+parse_bin(const char *srcbuf, uint64_t *res)
+{
+ uint64_t uintres = 0;
+ enum {
+ ERROR,
+ START,
+ BIN,
+ ZERO_OK,
+ BIN_OK,
+ } st = START;
const char * buf;
char c;
- uint64_t res1 = 0;
-
- if (!tk)
- return -1;
-
- if (!srcbuf || !*srcbuf)
- return -1;
buf = srcbuf;
c = *buf;
-
- memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
-
- /* check that we have enough room in res */
- if (res) {
- if (check_res_size(&nd, ressize) < 0)
- return -1;
- }
-
while (st != ERROR && c && !cmdline_isendoftoken(c)) {
debug_printf("%c %x -> ", c, c);
switch (st) {
case START:
- if (c == '-') {
- st = DEC_NEG;
- }
- else if (c == '0') {
+ if (c == '0') {
st = ZERO_OK;
}
- else if (c >= '1' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = DEC_POS_OK;
- }
- else {
+ else {
st = ERROR;
}
break;
case ZERO_OK:
- if (c == 'x') {
- st = HEX;
- }
- else if (c == 'b') {
+ if (c == 'b') {
st = BIN;
}
- else if (c >= '0' && c <= '7') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = OCTAL_OK;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_NEG:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = DEC_NEG_OK;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_NEG_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_POS_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
- case HEX:
- st = HEX_OK;
- /* fall-through */
- case HEX_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 16) < 0)
- st = ERROR;
- }
- else if (c >= 'a' && c <= 'f') {
- if (add_to_res(c - 'a' + 10, &res1, 16) < 0)
- st = ERROR;
- }
- else if (c >= 'A' && c <= 'F') {
- if (add_to_res(c - 'A' + 10, &res1, 16) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
-
- case OCTAL_OK:
- if (c >= '0' && c <= '7') {
- if (add_to_res(c - '0', &res1, 8) < 0)
- st = ERROR;
- }
else {
st = ERROR;
}
@@ -230,7 +193,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
/* fall-through */
case BIN_OK:
if (c >= '0' && c <= '1') {
- if (add_to_res(c - '0', &res1, 2) < 0)
+ if (add_to_bin(c - '0', &uintres) < 0)
st = ERROR;
}
else {
@@ -239,10 +202,10 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
break;
default:
debug_printf("not impl ");
-
+ st = ERROR;
}
- debug_printf("(%"PRIu64")\n", res1);
+ debug_printf("(%"PRIu64")\n", uintres);
buf ++;
c = *buf;
@@ -252,66 +215,92 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
return -1;
}
- switch (st) {
- case ZERO_OK:
- case DEC_POS_OK:
- case HEX_OK:
- case OCTAL_OK:
- case BIN_OK:
- if (nd.type == RTE_INT8 && res1 <= INT8_MAX) {
- if (res) *(int8_t *)res = (int8_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT16 && res1 <= INT16_MAX) {
- if (res) *(int16_t *)res = (int16_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT32 && res1 <= INT32_MAX) {
- if (res) *(int32_t *)res = (int32_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT64 && res1 <= INT64_MAX) {
- if (res) *(int64_t *)res = (int64_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT8 && res1 <= UINT8_MAX) {
- if (res) *(uint8_t *)res = (uint8_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT16 && res1 <= UINT16_MAX) {
- if (res) *(uint16_t *)res = (uint16_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT32 && res1 <= UINT32_MAX) {
- if (res) *(uint32_t *)res = (uint32_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT64) {
- if (res) *(uint64_t *)res = res1;
- return buf-srcbuf;
- } else {
- return -1;
- }
- break;
+ if (st != BIN_OK)
+ return -1;
+
+ *res = uintres;
+ return buf - srcbuf;
+}
- case DEC_NEG_OK:
- if (nd.type == RTE_INT8 &&
- res1 <= INT8_MAX + 1) {
- if (res) *(int8_t *)res = (int8_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT16 &&
- res1 <= (uint16_t)INT16_MAX + 1) {
- if (res) *(int16_t *)res = (int16_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT32 &&
- res1 <= (uint32_t)INT32_MAX + 1) {
- if (res) *(int32_t *)res = (int32_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT64 &&
- res1 <= (uint64_t)INT64_MAX + 1) {
- if (res) *(int64_t *)res = (int64_t) (-res1);
- return buf-srcbuf;
- } else {
- return -1;
- }
+static int
+write_num(enum cmdline_numtype type, void *res, uint64_t uintres)
+{
+ switch (type) {
+ case RTE_UINT8:
+ *(uint8_t *)res = (uint8_t)uintres;
+ break;
+ case RTE_UINT16:
+ *(uint16_t *)res = (uint16_t)uintres;
+ break;
+ case RTE_UINT32:
+ *(uint32_t *)res = (uint32_t)uintres;
+ break;
+ case RTE_UINT64:
+ *(uint64_t *)res = uintres;
+ break;
+ case RTE_INT8:
+ *(int8_t *)res = (int8_t)uintres;
+ break;
+ case RTE_INT16:
+ *(int16_t *)res = (int16_t)uintres;
+ break;
+ case RTE_INT32:
+ *(int32_t *)res = (int32_t)uintres;
+ break;
+ case RTE_INT64:
+ *(int64_t *)res = (int64_t)uintres;
break;
default:
- debug_printf("error\n");
+ debug_printf("Wrong number type\n");
return -1;
}
+ return 0;
+}
+
+/* parse an int */
+RTE_EXPORT_SYMBOL(cmdline_parse_num)
+int
+cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
+ unsigned ressize)
+{
+ struct cmdline_token_num_data nd;
+
+ if (!tk)
+ return -1;
+
+ if (!srcbuf || !*srcbuf)
+ return -1;
+
+ memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
+
+ /* check that we have enough room in res */
+ if (res && check_res_size(&nd, ressize) < 0)
+ return -1;
+
+ if (nd.type >= RTE_UINT8 && nd.type <= RTE_INT64) {
+ int ret, neg = *srcbuf == '-';
+ uint64_t uintres;
+
+ /* try parsing as number */
+ ret = parse_num(srcbuf, &uintres);
+
+ if (ret < 0) {
+ /* parse failed, try parsing as binary */
+ ret = parse_bin(srcbuf, &uintres);
+ if (ret < 0)
+ return -1;
+ }
+ /* check if we're within valid range */
+ if (check_parsed_num(nd.type, neg, uintres) < 0)
+ return -1;
+
+ /* parsing succeeded, write the value if necessary */
+ if (res && write_num(nd.type, res, uintres) < 0)
+ return -1;
+
+ return ret;
+ }
+ return -1;
}
@@ -328,9 +317,9 @@ cmdline_get_help_num(cmdline_parse_token_hdr_t *tk, char *dstbuf, unsigned int s
memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
- /* should not happen.... don't so this test */
- /* if (nd.type >= (sizeof(num_help)/sizeof(const char *))) */
- /* return -1; */
+ /* ensure we have a valid help message for the given type */
+ if (nd.type >= RTE_DIM(num_help))
+ return -1;
ret = strlcpy(dstbuf, num_help[nd.type], size);
if (ret < 0)
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v8 2/3] cmdline: add floating point support
2025-05-08 13:16 ` [PATCH v8 1/3] cmdline: use C standard library as number parser Anatoly Burakov
@ 2025-05-08 13:16 ` Anatoly Burakov
2025-05-08 13:16 ` [PATCH v8 3/3] app/testpmd: add sleep command Anatoly Burakov
2025-05-09 13:02 ` [PATCH v8 1/3] cmdline: use C standard library as number parser Burakov, Anatoly
2 siblings, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-08 13:16 UTC (permalink / raw)
To: dev
Add support for parsing floating point numbers in cmdline library, as well
as unit tests for the new functionality. Use C library for parsing.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v7 -> v8:
- Fixed leftover comment about needing bigger epsilon value
- Fixed debug prints to avoid indexing num_help[] array
v6 -> v7:
- Fixed a bug in float compare in unit tests where a bigger epsilon
value than necessary was "needed" because we were comparing float
result to a double result
v5 -> v6:
- Small refactor to reduce amount of noise
- Use strtof for parsing single precision floats
- More unit tests
v4 -> v5:
- Reworked to use standard C library functions as much as possible,
keeping near-100% compatibility with earlier versions (the only
difference is that strings like "+4" are now considered valid)
v3 -> v4:
- Removed unnecessary check for integer overflow when parsing negative
floats (as we convert to double before changing sign)
- Make naming of float exponent states more consistent
v2 -> v3:
- Fixed a bug where a free-standing negative exponent ("1e-") would attempt to be
parsed, and added unit tests for this case
- Added support for floats in dpdk-cmdline-gen script
- Added documentation updates to call out float support
app/test/test_cmdline_num.c | 283 ++++++++++++++++++++++++-
buildtools/dpdk-cmdline-gen.py | 24 ++-
doc/guides/prog_guide/cmdline.rst | 3 +
doc/guides/rel_notes/release_25_07.rst | 5 +
lib/cmdline/cmdline_parse_num.c | 66 +++++-
lib/cmdline/cmdline_parse_num.h | 4 +-
6 files changed, 366 insertions(+), 19 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index dd70f6f824..dcccd760ed 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -5,6 +5,8 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <rte_string_fns.h>
@@ -23,6 +25,11 @@ struct num_signed_str {
int64_t result;
};
+struct num_float_str {
+ const char * str;
+ double result;
+};
+
const struct num_unsigned_str num_valid_positive_strs[] = {
/* decimal positive */
{"0", 0 },
@@ -143,6 +150,63 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-01000000000000000000000", INT64_MIN },
};
+const struct num_float_str float_valid_strs[] = {
+ /* zero */
+ {"0", 0},
+ /* parse int as float */
+ {"1", 1},
+ {"-1", -1},
+ /* fractional */
+ {"1.23", 1.23},
+ {"-1.23", -1.23},
+ {"0.123", 0.123},
+ {"-0.123", -0.123},
+ {"123.456", 123.456},
+ {"-123.456", -123.456},
+ /* positive exponent */
+ {"1e2", 1e2},
+ {"-1e2", -1e2},
+ {"1E2", 1E2},
+ {"-1E2", -1E2},
+ {"0.12e3", 0.12e3},
+ {"-0.12e3", -0.12e3},
+ {"1.23e4", 1.23e4},
+ {"-1.23e4", -1.23e4},
+ {"1.23E4", 1.23E4},
+ {"-1.23E4", -1.23E4},
+ {"123.456e7", 123.456e7},
+ {"-123.456e7", -123.456e7},
+ {"123.456E7", 123.456E7},
+ {"-123.456E7", -123.456E7},
+ /* negative exponent */
+ {"1e-2", 1e-2},
+ {"-1e-2", -1e-2},
+ {"1E-2", 1E-2},
+ {"-1E-2", -1E-2},
+ {"0.12e-3", 0.12e-3},
+ {"-0.12e-3", -0.12e-3},
+ {"1.23e-4", 1.23e-4},
+ {"-1.23e-4", -1.23e-4},
+ {"1.23E-4", 1.23E-4},
+ {"-1.23E-4", -1.23E-4},
+ {"123.456e-7", 123.456e-7},
+ {"-123.456e-7", -123.456e-7},
+ {"123.456E-7", 123.456E-7},
+ {"-123.456E-7", -123.456E-7},
+ /* try overflowing float */
+ {"2e63", 2e63},
+ {"-2e63", -2e63},
+ {"2E63", 2E63},
+ {"-2E63", -2E63},
+ {"18446744073709551615", (double) UINT64_MAX},
+ {"-9223372036854775808", (double) INT64_MIN},
+ /* try overflowing double */
+ {"2e308", HUGE_VAL},
+ {"-2e308", -HUGE_VAL},
+ {"2E308", HUGE_VAL},
+ {"-2E308", HUGE_VAL},
+};
+
const struct num_unsigned_str num_garbage_positive_strs[] = {
/* valid strings with garbage on the end, should still be valid */
/* decimal */
@@ -200,6 +264,71 @@ const struct num_signed_str num_garbage_negative_strs[] = {
{"-01000000000000000000000 garbage", INT64_MIN },
};
+const struct num_float_str float_garbage_strs[] = {
+ /* valid strings with garbage on the end, should still be valid */
+ /* positive float positive exponent */
+ {"123.456e7\0garbage", 123.456e7},
+ {"123.456e7\rgarbage", 123.456e7},
+ {"123.456e7\tgarbage", 123.456e7},
+ {"123.456e7\ngarbage", 123.456e7},
+ {"123.456e7#garbage", 123.456e7},
+ {"123.456e7 garbage", 123.456e7},
+ /* negative float positive exponent */
+ {"-123.456e7\0garbage", -123.456e7},
+ {"-123.456e7\rgarbage", -123.456e7},
+ {"-123.456e7\tgarbage", -123.456e7},
+ {"-123.456e7\ngarbage", -123.456e7},
+ {"-123.456e7#garbage", -123.456e7},
+ {"-123.456e7 garbage", -123.456e7},
+ /* positive float negative exponent */
+ {"123.456e-7\0garbage", 123.456e-7},
+ {"123.456e-7\rgarbage", 123.456e-7},
+ {"123.456e-7\tgarbage", 123.456e-7},
+ {"123.456e-7\ngarbage", 123.456e-7},
+ {"123.456e-7#garbage", 123.456e-7},
+ {"123.456e-7 garbage", 123.456e-7},
+ /* negative float negative exponent */
+ {"-123.456e-7\0garbage", -123.456e-7},
+ {"-123.456e-7\rgarbage", -123.456e-7},
+ {"-123.456e-7\tgarbage", -123.456e-7},
+ {"-123.456e-7\ngarbage", -123.456e-7},
+ {"-123.456e-7#garbage", -123.456e-7},
+ {"-123.456e-7 garbage", -123.456e-7},
+ /* float overflows */
+ {"18446744073709551615\0garbage", (double) UINT64_MAX},
+ {"18446744073709551615\rgarbage", (double) UINT64_MAX},
+ {"18446744073709551615\ngarbage", (double) UINT64_MAX},
+ {"18446744073709551615\tgarbage", (double) UINT64_MAX},
+ {"18446744073709551615#garbage", (double) UINT64_MAX},
+ {"18446744073709551615 garbage", (double) UINT64_MAX},
+ {"-9223372036854775808\0garbage", (double) INT64_MIN},
+ {"-9223372036854775808\rgarbage", (double) INT64_MIN},
+ {"-9223372036854775808\ngarbage", (double) INT64_MIN},
+ {"-9223372036854775808\tgarbage", (double) INT64_MIN},
+ {"-9223372036854775808#garbage", (double) INT64_MIN},
+ {"-9223372036854775808 garbage", (double) INT64_MIN},
+};
+
+const char *float_invalid_strs[] = {
+ "1.1.",
+ "1.1.1",
+ "-1.1.",
+ "-1.1.1",
+ "e",
+ "1e",
+ "-1e",
+ "0.1e",
+ "-0.1e",
+ "1.e",
+ "-1.e",
+ "1.23e3.4",
+ "-1.23e3.4",
+ "1e1e",
+ "1e1e1",
+ "1e-",
+ "-1e-"
+};
+
const char * num_invalid_strs[] = {
"18446744073709551616", /* out of range unsigned */
"-9223372036854775809", /* out of range negative signed */
@@ -218,7 +347,16 @@ const char * num_invalid_strs[] = {
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
"1111000011110000111100001111000011110000111100001111000011110000"),
+ /* valid float values but should fail to parse as ints */
"1E3",
+ "-1E3",
+ "1.23",
+ "-1.23",
+ "1E-3",
+ "-1E-3",
+ "1.23E4",
+ "-1.23E4",
+ /* misc invalid values */
"0A",
"-B",
"1.23G",
@@ -231,6 +369,48 @@ const char * num_invalid_strs[] = {
"\0",
};
+static int
+float_cmp(double expected, void *actual_p, enum cmdline_numtype type)
+{
+ double eps;
+ double actual_d;
+
+ if (type == RTE_FLOAT_SINGLE) {
+ /* read as float, convert to double */
+ actual_d = (double)*(float *)actual_p;
+ /* downcast expected to float as well */
+ expected = (double)(float)expected;
+ eps = FLT_EPSILON;
+ } else {
+ /* read as double */
+ actual_d = *(double *)actual_p;
+ eps = DBL_EPSILON;
+ }
+ /* compare using epsilon value */
+ if (fabs(expected - actual_d) < eps)
+ return 0;
+ /* not equal */
+ return expected < actual_d ? -1 : 1;
+}
+
+static int
+can_parse_float(double expected_result, enum cmdline_numtype type)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ if (expected_result > FLT_MAX || expected_result < -FLT_MAX)
+ return 0;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (expected_result > DBL_MAX || expected_result < -DBL_MAX)
+ return 0;
+ break;
+ default:
+ return 1;
+ }
+ return 1;
+}
+
static int
can_parse_unsigned(uint64_t expected_result, enum cmdline_numtype type)
{
@@ -386,11 +566,11 @@ test_parse_num_invalid_data(void)
int ret = 0;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result; /* pick largest buffer */
cmdline_parse_token_num_t token;
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result; /* pick largest buffer */
token.num_data.type = type;
/* test full strings */
@@ -412,6 +592,31 @@ test_parse_num_invalid_data(void)
}
}
}
+
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result; /* pick largest buffer */
+ token.num_data.type = type;
+
+ /* test full strings */
+ for (i = 0; i < RTE_DIM(float_invalid_strs); i++) {
+
+ memset(&result, 0, sizeof(double));
+ memset(&buf, 0, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*)&token,
+ float_invalid_strs[i], (void*)&result, sizeof(result));
+ if (ret != -1) {
+ /* get some info about what we are trying to parse */
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ printf("Error: parsing %s as %s succeeded!\n",
+ float_invalid_strs[i], buf);
+ return -1;
+ }
+ }
+ }
return 0;
}
@@ -423,13 +628,13 @@ test_parse_num_valid(void)
enum cmdline_numtype type;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result;
cmdline_parse_token_num_t token;
/** valid strings **/
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive strings */
@@ -468,7 +673,7 @@ test_parse_num_valid(void)
cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
buf, sizeof(buf));
- ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
num_valid_negative_strs[i].str,
(void*)&result, sizeof(result));
@@ -504,10 +709,44 @@ test_parse_num_valid(void)
}
}
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(float_valid_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ float_valid_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(float_valid_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ float_valid_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(float_valid_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ float_valid_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
/** garbage strings **/
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive garbage strings */
@@ -585,6 +824,38 @@ test_parse_num_valid(void)
}
}
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(float_garbage_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ float_garbage_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(float_garbage_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ float_garbage_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(float_garbage_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ float_garbage_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
memset(&buf, 0, sizeof(buf));
/* coverage! */
diff --git a/buildtools/dpdk-cmdline-gen.py b/buildtools/dpdk-cmdline-gen.py
index 7dadded783..6c76d7116a 100755
--- a/buildtools/dpdk-cmdline-gen.py
+++ b/buildtools/dpdk-cmdline-gen.py
@@ -17,16 +17,18 @@
RTE_SET_USED(cl);
RTE_SET_USED(data);
"""
-NUMERIC_TYPES = [
- "UINT8",
- "UINT16",
- "UINT32",
- "UINT64",
- "INT8",
- "INT16",
- "INT32",
- "INT64",
-]
+NUMERIC_TYPES = {
+ "UINT8": "uint8_t",
+ "UINT16": "uint16_t",
+ "UINT32": "uint32_t",
+ "UINT64": "uint64_t",
+ "INT8": "int8_t",
+ "INT16": "int16_t",
+ "INT32": "int32_t",
+ "INT64": "int64_t",
+ "FLOAT_SINGLE": "float",
+ "FLOAT_DOUBLE": "double",
+}
def process_command(lineno, tokens, comment):
@@ -70,7 +72,7 @@ def process_command(lineno, tokens, comment):
f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});"
)
elif t_type in NUMERIC_TYPES:
- result_struct.append(f"\t{t_type.lower()}_t {t_name};")
+ result_struct.append(f"\t{NUMERIC_TYPES[t_type]} {t_name};")
initializers.append(
f"static cmdline_parse_token_num_t cmd_{name}_{t_name}_tok =\n"
f"\tTOKEN_NUM_INITIALIZER(struct cmd_{name}_result, {t_name}, RTE_{t_type});"
diff --git a/doc/guides/prog_guide/cmdline.rst b/doc/guides/prog_guide/cmdline.rst
index e20281ceb5..447a90e32e 100644
--- a/doc/guides/prog_guide/cmdline.rst
+++ b/doc/guides/prog_guide/cmdline.rst
@@ -22,6 +22,7 @@ The DPDK command-line library supports the following features:
* Strings
* Signed/unsigned 16/32/64-bit integers
+ * Single/double precision floats
* IP Addresses
* Ethernet Addresses
@@ -68,6 +69,8 @@ The format of the list file must be:
* ``<UINT16>port_id``
+ * ``<FLOAT_SINGLE>ratio``
+
* ``<IP>src_ip``
* ``<IPv4>dst_ip4``
diff --git a/doc/guides/rel_notes/release_25_07.rst b/doc/guides/rel_notes/release_25_07.rst
index 093b85d206..54bc545110 100644
--- a/doc/guides/rel_notes/release_25_07.rst
+++ b/doc/guides/rel_notes/release_25_07.rst
@@ -55,6 +55,11 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added floating point numbers support to cmdline library.**
+
+ The cmdline library now supports parsing single- and double-precision
+ floating point numbers in interactive user commands.
+
Removed Items
-------------
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index 1e04a59038..e5161de572 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -8,6 +8,8 @@
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <string.h>
#include <eal_export.h>
#include <rte_string_fns.h>
@@ -34,6 +36,7 @@ struct cmdline_token_ops cmdline_token_num_ops = {
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
"INT8", "INT16", "INT32", "INT64",
+ "FLOAT_SINGLE", "FLOAT_DOUBLE"
};
static inline int
@@ -71,6 +74,14 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
if (ressize < sizeof(int64_t))
return -1;
break;
+ case RTE_FLOAT_SINGLE:
+ if (ressize < sizeof(float))
+ return -1;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (ressize < sizeof(double))
+ return -1;
+ break;
default:
debug_printf("Wrong number type: %d\n", nd->type);
return -1;
@@ -151,6 +162,26 @@ parse_num(const char *srcbuf, uint64_t *resptr)
return end - srcbuf;
}
+static int
+parse_float(const char *srcbuf, enum cmdline_numtype type, double *resptr)
+{
+ double dres;
+ char *end;
+
+ if (type == RTE_FLOAT_SINGLE)
+ dres = (double)strtof(srcbuf, &end);
+ else if (type == RTE_FLOAT_DOUBLE)
+ dres = strtod(srcbuf, &end);
+ else
+ return -1;
+
+ if (end == srcbuf || !cmdline_isendoftoken(*end) || isinf(dres))
+ return -1;
+
+ *resptr = dres;
+ return end - srcbuf;
+}
+
static int
parse_bin(const char *srcbuf, uint64_t *res)
{
@@ -257,7 +288,24 @@ write_num(enum cmdline_numtype type, void *res, uint64_t uintres)
return 0;
}
-/* parse an int */
+static int
+write_float(enum cmdline_numtype type, void *res, double dres)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ *(float *)res = (float)dres;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ *(double *)res = dres;
+ break;
+ default:
+ debug_printf("Wrong float type\n");
+ return -1;
+ }
+ return 0;
+}
+
+/* parse a number */
RTE_EXPORT_SYMBOL(cmdline_parse_num)
int
cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
@@ -277,6 +325,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
if (res && check_res_size(&nd, ressize) < 0)
return -1;
+ /* integer parsing */
if (nd.type >= RTE_UINT8 && nd.type <= RTE_INT64) {
int ret, neg = *srcbuf == '-';
uint64_t uintres;
@@ -298,6 +347,21 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
if (res && write_num(nd.type, res, uintres) < 0)
return -1;
+ return ret;
+ /* float parsing */
+ } else if (nd.type >= RTE_FLOAT_SINGLE && nd.type <= RTE_FLOAT_DOUBLE) {
+ double dres;
+ int ret;
+
+ /* try parsing as float */
+ ret = parse_float(srcbuf, nd.type, &dres);
+ if (ret < 0)
+ return -1;
+
+ /* parsing succeeded, write the value if necessary */
+ if (res && write_float(nd.type, res, dres))
+ return -1;
+
return ret;
}
return -1;
diff --git a/lib/cmdline/cmdline_parse_num.h b/lib/cmdline/cmdline_parse_num.h
index bdd0267612..b2792a2d11 100644
--- a/lib/cmdline/cmdline_parse_num.h
+++ b/lib/cmdline/cmdline_parse_num.h
@@ -22,7 +22,9 @@ enum cmdline_numtype {
RTE_INT8,
RTE_INT16,
RTE_INT32,
- RTE_INT64
+ RTE_INT64,
+ RTE_FLOAT_SINGLE,
+ RTE_FLOAT_DOUBLE,
};
struct cmdline_token_num_data {
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v8 3/3] app/testpmd: add sleep command
2025-05-08 13:16 ` [PATCH v8 1/3] cmdline: use C standard library as number parser Anatoly Burakov
2025-05-08 13:16 ` [PATCH v8 2/3] cmdline: add floating point support Anatoly Burakov
@ 2025-05-08 13:16 ` Anatoly Burakov
2025-05-09 13:02 ` [PATCH v8 1/3] cmdline: use C standard library as number parser Burakov, Anatoly
2 siblings, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-08 13:16 UTC (permalink / raw)
To: dev, Aman Singh
Test-pmd already has a way to run a list of commands from file, but there
is no way to pause execution for a specified amount of time between two
commands. This may be necessary for simple automation, particularly for
waiting on some asynchronous operation such as link status update.
Add a simple sleep command to wait until certain number of seconds has
passed, using the newly added cmdline library floating point support.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v1 -> v2:
- Add floating point support to cmdline
- Use floating point format for pause command
app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index d4bb3ec998..b6152c07e6 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
"quit\n"
" Quit to prompt.\n\n"
+
+ "sleep secs\n"
+ " Sleep for secs seconds (can be fractional).\n\n"
);
}
@@ -7768,6 +7771,37 @@ static cmdline_parse_inst_t cmd_quit = {
},
};
+/* *** SLEEP *** */
+struct cmd_sleep_result {
+ cmdline_fixed_string_t sleep;
+ double secs;
+};
+
+static void cmd_sleep_parsed(void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_sleep_result *res = parsed_result;
+
+ rte_delay_us_sleep(res->secs * 1E6);
+}
+
+static cmdline_parse_token_string_t cmd_sleep_sleep =
+ TOKEN_STRING_INITIALIZER(struct cmd_sleep_result, sleep, "sleep");
+static cmdline_parse_token_num_t cmd_sleep_seconds =
+ TOKEN_NUM_INITIALIZER(struct cmd_sleep_result, secs, RTE_FLOAT_DOUBLE);
+
+static cmdline_parse_inst_t cmd_sleep = {
+ .f = cmd_sleep_parsed,
+ .data = NULL,
+ .help_str = "sleep <secs>: Sleep for a specified number of seconds",
+ .tokens = {
+ (void *)&cmd_sleep_sleep,
+ (void *)&cmd_sleep_seconds,
+ NULL,
+ },
+};
+
/* *** ADD/REMOVE MAC ADDRESS FROM A PORT *** */
struct cmd_mac_addr_result {
cmdline_fixed_string_t mac_addr_cmd;
@@ -13711,6 +13745,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_showdevice,
&cmd_showcfg,
&cmd_showfwdall,
+ &cmd_sleep,
&cmd_start,
&cmd_start_tx_first,
&cmd_start_tx_first_n,
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v8 1/3] cmdline: use C standard library as number parser
2025-05-08 13:16 ` [PATCH v8 1/3] cmdline: use C standard library as number parser Anatoly Burakov
2025-05-08 13:16 ` [PATCH v8 2/3] cmdline: add floating point support Anatoly Burakov
2025-05-08 13:16 ` [PATCH v8 3/3] app/testpmd: add sleep command Anatoly Burakov
@ 2025-05-09 13:02 ` Burakov, Anatoly
2025-05-09 13:08 ` Burakov, Anatoly
2 siblings, 1 reply; 45+ messages in thread
From: Burakov, Anatoly @ 2025-05-09 13:02 UTC (permalink / raw)
To: dev
On 5/8/2025 3:16 PM, Anatoly Burakov wrote:
> Remove custom number parser and use C standard library instead. In order to
> keep compatibility with earlier versions of the parser, we have to take
> into account a couple of quirks:
>
> - We did not consider "negative" numbers to be valid for anything other
> than base-10 numbers, whereas C standard library does. Adjust the tests
> to match the new behavior.
> - We did not consider numbers such as "+4" to be valid, whereas C
> standard library does. Adjust the tests to match the new behavior.
> - C standard library's strtoull does not do range checks on negative
> numbers, so we have to parse knowingly-negative numbers as signed.
> - C standard library does not support binary numbers, so we keep around the
> relevant parts of the custom parser in place to support binary numbers.
>
> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> ---
>
> Notes:
> v7 -> v8:
> - Added the commented-out out-of-bounds check back
> - Replaced debug print messages to ensure they don't attempt to
> index the num_help[] array (should fix compile errors)
>
> v5 -> v6:
> - Allowed more negative numbers (such as negative octals or hex)
> - Updated unit tests to check new cases
> - Small refactoring of code to reduce amount of noise
> - More verbose debug output
>
> v4 -> v5:
> - Added this commit
There is a unit test failure coming specifically from this commit, that
only happens on ARM. Log:
Error: parsing -0b0111010101 as INT16 succeeded!
That is, when confronted with a negative binary string, it seems that
strtoll will report success, whereas other platforms report failure. I'm
confused, is libc strtoll different on ARM? I don't have an ARM platform
available to test this so I don't know why this is happening.
--
Thanks,
Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v8 1/3] cmdline: use C standard library as number parser
2025-05-09 13:02 ` [PATCH v8 1/3] cmdline: use C standard library as number parser Burakov, Anatoly
@ 2025-05-09 13:08 ` Burakov, Anatoly
2025-05-09 13:27 ` Burakov, Anatoly
0 siblings, 1 reply; 45+ messages in thread
From: Burakov, Anatoly @ 2025-05-09 13:08 UTC (permalink / raw)
To: dev
On 5/9/2025 3:02 PM, Burakov, Anatoly wrote:
> On 5/8/2025 3:16 PM, Anatoly Burakov wrote:
>> Remove custom number parser and use C standard library instead. In
>> order to
>> keep compatibility with earlier versions of the parser, we have to take
>> into account a couple of quirks:
>>
>> - We did not consider "negative" numbers to be valid for anything other
>> than base-10 numbers, whereas C standard library does. Adjust the
>> tests
>> to match the new behavior.
>> - We did not consider numbers such as "+4" to be valid, whereas C
>> standard library does. Adjust the tests to match the new behavior.
>> - C standard library's strtoull does not do range checks on negative
>> numbers, so we have to parse knowingly-negative numbers as signed.
>> - C standard library does not support binary numbers, so we keep
>> around the
>> relevant parts of the custom parser in place to support binary
>> numbers.
>>
>> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
>> ---
>>
>> Notes:
>> v7 -> v8:
>> - Added the commented-out out-of-bounds check back
>> - Replaced debug print messages to ensure they don't attempt to
>> index the num_help[] array (should fix compile errors)
>> v5 -> v6:
>> - Allowed more negative numbers (such as negative octals or hex)
>> - Updated unit tests to check new cases
>> - Small refactoring of code to reduce amount of noise
>> - More verbose debug output
>> v4 -> v5:
>> - Added this commit
>
> There is a unit test failure coming specifically from this commit, that
> only happens on ARM. Log:
>
> Error: parsing -0b0111010101 as INT16 succeeded!
>
> That is, when confronted with a negative binary string, it seems that
> strtoll will report success, whereas other platforms report failure. I'm
> confused, is libc strtoll different on ARM? I don't have an ARM platform
> available to test this so I don't know why this is happening.
>
Correction: it seems that newer libc versions have added support for
binary formats. I'll therefore amend the tests to account for that.
--
Thanks,
Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v8 1/3] cmdline: use C standard library as number parser
2025-05-09 13:08 ` Burakov, Anatoly
@ 2025-05-09 13:27 ` Burakov, Anatoly
0 siblings, 0 replies; 45+ messages in thread
From: Burakov, Anatoly @ 2025-05-09 13:27 UTC (permalink / raw)
To: dev
On 5/9/2025 3:08 PM, Burakov, Anatoly wrote:
> On 5/9/2025 3:02 PM, Burakov, Anatoly wrote:
>> On 5/8/2025 3:16 PM, Anatoly Burakov wrote:
>>> Remove custom number parser and use C standard library instead. In
>>> order to
>>> keep compatibility with earlier versions of the parser, we have to take
>>> into account a couple of quirks:
>>>
>>> - We did not consider "negative" numbers to be valid for anything other
>>> than base-10 numbers, whereas C standard library does. Adjust the
>>> tests
>>> to match the new behavior.
>>> - We did not consider numbers such as "+4" to be valid, whereas C
>>> standard library does. Adjust the tests to match the new behavior.
>>> - C standard library's strtoull does not do range checks on negative
>>> numbers, so we have to parse knowingly-negative numbers as signed.
>>> - C standard library does not support binary numbers, so we keep
>>> around the
>>> relevant parts of the custom parser in place to support binary
>>> numbers.
>>>
>>> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
>>> ---
>>>
>>> Notes:
>>> v7 -> v8:
>>> - Added the commented-out out-of-bounds check back
>>> - Replaced debug print messages to ensure they don't attempt to
>>> index the num_help[] array (should fix compile errors)
>>> v5 -> v6:
>>> - Allowed more negative numbers (such as negative octals or hex)
>>> - Updated unit tests to check new cases
>>> - Small refactoring of code to reduce amount of noise
>>> - More verbose debug output
>>> v4 -> v5:
>>> - Added this commit
>>
>> There is a unit test failure coming specifically from this commit,
>> that only happens on ARM. Log:
>>
>> Error: parsing -0b0111010101 as INT16 succeeded!
>>
>> That is, when confronted with a negative binary string, it seems that
>> strtoll will report success, whereas other platforms report failure.
>> I'm confused, is libc strtoll different on ARM? I don't have an ARM
>> platform available to test this so I don't know why this is happening.
>>
>
> Correction: it seems that newer libc versions have added support for
> binary formats. I'll therefore amend the tests to account for that.
>
The specific announcement regarding binary formatted strings:
https://sourceware.org/pipermail/libc-alpha/2023-July/150524.html
Since our binary parser will be a fallback implementation starting from
that version of libc, I think it would be easier to just add negative
binary support to our custom parser than it would be to differentiate
between different libc versions, and support negative binary formats
regardless.
--
Thanks,
Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v9 1/3] cmdline: use C standard library as number parser
2025-05-02 12:27 [PATCH v1 1/1] app/testpmd: add sleep command Anatoly Burakov
` (8 preceding siblings ...)
2025-05-08 13:16 ` [PATCH v8 1/3] cmdline: use C standard library as number parser Anatoly Burakov
@ 2025-05-09 13:39 ` Anatoly Burakov
2025-05-09 13:39 ` [PATCH v9 2/3] cmdline: add floating point support Anatoly Burakov
` (2 more replies)
2025-05-09 14:41 ` [PATCH v10 " Anatoly Burakov
10 siblings, 3 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-09 13:39 UTC (permalink / raw)
To: dev
Remove custom number parser and use C standard library instead. In order to
keep compatibility with earlier versions of the parser, we have to take
into account a couple of quirks:
- We did not consider "negative" numbers to be valid for anything other
than base-10 numbers, whereas C standard library does. Adjust the tests
to match the new behavior.
- We did not consider numbers such as "+4" to be valid, whereas C
standard library does. Adjust the tests to match the new behavior.
- C standard library's strtoull does not do range checks on negative
numbers, so we have to parse knowingly-negative numbers as signed.
- C standard library does not support binary numbers, so we keep around the
relevant parts of the custom parser in place to support binary numbers.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v8 -> v9:
- glibc 2.38 supports binary formats (0bxxxx and -0bxxxx) under certain conditions,
so in order to ensure the same functionality on all glic versions, add support for
positive and negative binary formats, and adjust tests accordingly
v7 -> v8:
- Added the commented-out out-of-bounds check back
- Replaced debug print messages to ensure they don't attempt to
index the num_help[] array (should fix compile errors)
v5 -> v6:
- Allowed more negative numbers (such as negative octals or hex)
- Updated unit tests to check new cases
- Small refactoring of code to reduce amount of noise
- More verbose debug output
v4 -> v5:
- Added this commit
app/test/test_cmdline_num.c | 34 ++-
lib/cmdline/cmdline_parse_num.c | 382 ++++++++++++++++----------------
2 files changed, 225 insertions(+), 191 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index 9276de59bd..100c564188 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -139,6 +139,9 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-2147483648", INT32_MIN },
{"-2147483649", INT32_MIN - 1LL },
{"-9223372036854775808", INT64_MIN },
+ {"-0x8000000000000000", INT64_MIN },
+ {"-01000000000000000000000", INT64_MIN },
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000", INT64_MIN },
};
const struct num_unsigned_str num_garbage_positive_strs[] = {
@@ -175,12 +178,40 @@ const struct num_unsigned_str num_garbage_positive_strs[] = {
const struct num_signed_str num_garbage_negative_strs[] = {
/* valid strings with garbage on the end, should still be valid */
+ /* negative numbers */
{"-9223372036854775808\0garbage", INT64_MIN },
{"-9223372036854775808\rgarbage", INT64_MIN },
{"-9223372036854775808\tgarbage", INT64_MIN },
{"-9223372036854775808\ngarbage", INT64_MIN },
{"-9223372036854775808#garbage", INT64_MIN },
{"-9223372036854775808 garbage", INT64_MIN },
+ /* negative hex */
+ {"-0x8000000000000000\0garbage", INT64_MIN },
+ {"-0x8000000000000000\rgarbage", INT64_MIN },
+ {"-0x8000000000000000\tgarbage", INT64_MIN },
+ {"-0x8000000000000000\ngarbage", INT64_MIN },
+ {"-0x8000000000000000#garbage", INT64_MIN },
+ {"-0x8000000000000000 garbage", INT64_MIN },
+ /* negative binary */
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000\0garbage",
+ INT64_MIN },
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000\rgarbage",
+ INT64_MIN },
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000\tgarbage",
+ INT64_MIN },
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000\ngarbage",
+ INT64_MIN },
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000#garbage",
+ INT64_MIN },
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000 garbage",
+ INT64_MIN },
+ /* negative octal */
+ {"-01000000000000000000000\0garbage", INT64_MIN },
+ {"-01000000000000000000000\rgarbage", INT64_MIN },
+ {"-01000000000000000000000\tgarbage", INT64_MIN },
+ {"-01000000000000000000000\ngarbage", INT64_MIN },
+ {"-01000000000000000000000#garbage", INT64_MIN },
+ {"-01000000000000000000000 garbage", INT64_MIN },
};
const char * num_invalid_strs[] = {
@@ -197,15 +228,12 @@ const char * num_invalid_strs[] = {
"0b01110101017001",
/* false negative numbers */
"-12345F623",
- "-0x1234580A",
- "-0b0111010101",
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
"1111000011110000111100001111000011110000111100001111000011110000"),
"1E3",
"0A",
"-B",
- "+4",
"1.23G",
"",
" ",
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index f21796bedb..8e5b75ff56 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -4,6 +4,7 @@
* All rights reserved.
*/
+#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
@@ -29,23 +30,6 @@ struct cmdline_token_ops cmdline_token_num_ops = {
.get_help = cmdline_get_help_num,
};
-enum num_parse_state_t {
- START,
- DEC_NEG,
- BIN,
- HEX,
-
- ERROR,
-
- FIRST_OK, /* not used */
- ZERO_OK,
- HEX_OK,
- OCTAL_OK,
- BIN_OK,
- DEC_NEG_OK,
- DEC_POS_OK,
-};
-
/* Keep it sync with enum in .h */
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
@@ -53,13 +37,13 @@ static const char * num_help[] = {
};
static inline int
-add_to_res(unsigned int c, uint64_t *res, unsigned int base)
+add_to_bin(unsigned int c, uint64_t *res)
{
/* overflow */
- if ((UINT64_MAX - c) / base < *res)
+ if ((UINT64_MAX - c) / 2 < *res)
return -1;
- *res = (uint64_t) (*res * base + c);
+ *res = (uint64_t) (*res * 2 + c);
return 0;
}
@@ -88,138 +72,131 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
return -1;
break;
default:
+ debug_printf("Wrong number type: %d\n", nd->type);
return -1;
}
return 0;
}
-/* parse an int */
-RTE_EXPORT_SYMBOL(cmdline_parse_num)
-int
-cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
- unsigned ressize)
+static int
+check_parsed_num(enum cmdline_numtype type, int neg, uint64_t uintres)
{
- struct cmdline_token_num_data nd;
- enum num_parse_state_t st = START;
+ int lo_ok, hi_ok;
+
+ switch (type) {
+ case RTE_UINT8:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT8_MAX;
+ break;
+ case RTE_UINT16:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT16_MAX;
+ break;
+ case RTE_UINT32:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT32_MAX;
+ break;
+ case RTE_UINT64:
+ lo_ok = !neg;
+ hi_ok = 1; /* can't be out of range if parsed successfully */
+ break;
+ case RTE_INT8:
+ lo_ok = !neg || (int64_t)uintres >= INT8_MIN;
+ hi_ok = neg || uintres <= INT8_MAX;
+ break;
+ case RTE_INT16:
+ lo_ok = !neg || (int64_t)uintres >= INT16_MIN;
+ hi_ok = neg || uintres <= INT16_MAX;
+ break;
+ case RTE_INT32:
+ lo_ok = !neg || (int64_t)uintres >= INT32_MIN;
+ hi_ok = neg || uintres <= INT32_MAX;
+ break;
+ case RTE_INT64:
+ lo_ok = 1; /* can't be out of range if parsed successfully */
+ hi_ok = neg || uintres <= INT64_MAX;
+ break;
+ default:
+ debug_printf("Wrong number type\n");
+ return -1;
+ }
+ /* check ranges */
+ if (!lo_ok || !hi_ok)
+ return -1;
+ return 0;
+}
+
+static int
+parse_num(const char *srcbuf, uint64_t *resptr)
+{
+ uint64_t uintres;
+ char *end;
+ int neg = *srcbuf == '-';
+
+ /*
+ * strtoull does not do range checks on negative numbers, so we need to
+ * use strtoll if we know the value we're parsing looks like a negative
+ * one. we use base 0 for both, 0 means autodetect base.
+ */
+ errno = 0;
+ if (neg)
+ uintres = (uint64_t)strtoll(srcbuf, &end, 0);
+ else
+ uintres = strtoull(srcbuf, &end, 0);
+
+ if (end == srcbuf || !cmdline_isendoftoken(*end) || errno == ERANGE)
+ return -1;
+
+ *resptr = uintres;
+ return end - srcbuf;
+}
+
+static int
+parse_bin(const char *srcbuf, uint64_t *res)
+{
+ uint64_t uintres = 0;
+ enum {
+ ERROR,
+ START,
+ BIN,
+ NEG,
+ ZERO_OK,
+ BIN_OK,
+ } st = START;
const char * buf;
char c;
- uint64_t res1 = 0;
-
- if (!tk)
- return -1;
-
- if (!srcbuf || !*srcbuf)
- return -1;
+ int neg = 0;
buf = srcbuf;
c = *buf;
-
- memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
-
- /* check that we have enough room in res */
- if (res) {
- if (check_res_size(&nd, ressize) < 0)
- return -1;
- }
-
while (st != ERROR && c && !cmdline_isendoftoken(c)) {
debug_printf("%c %x -> ", c, c);
switch (st) {
case START:
- if (c == '-') {
- st = DEC_NEG;
- }
- else if (c == '0') {
+ if (c == '0') {
st = ZERO_OK;
+ } else if (c == '-') {
+ neg = 1;
+ st = NEG;
+ }
+ else {
+ st = ERROR;
}
- else if (c >= '1' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = DEC_POS_OK;
+ break;
+
+ case NEG:
+ if (c == '0') {
+ st = ZERO_OK;
}
- else {
+ else {
st = ERROR;
}
break;
case ZERO_OK:
- if (c == 'x') {
- st = HEX;
- }
- else if (c == 'b') {
+ if (c == 'b') {
st = BIN;
}
- else if (c >= '0' && c <= '7') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = OCTAL_OK;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_NEG:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = DEC_NEG_OK;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_NEG_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_POS_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
- case HEX:
- st = HEX_OK;
- /* fall-through */
- case HEX_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 16) < 0)
- st = ERROR;
- }
- else if (c >= 'a' && c <= 'f') {
- if (add_to_res(c - 'a' + 10, &res1, 16) < 0)
- st = ERROR;
- }
- else if (c >= 'A' && c <= 'F') {
- if (add_to_res(c - 'A' + 10, &res1, 16) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
-
- case OCTAL_OK:
- if (c >= '0' && c <= '7') {
- if (add_to_res(c - '0', &res1, 8) < 0)
- st = ERROR;
- }
else {
st = ERROR;
}
@@ -230,7 +207,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
/* fall-through */
case BIN_OK:
if (c >= '0' && c <= '1') {
- if (add_to_res(c - '0', &res1, 2) < 0)
+ if (add_to_bin(c - '0', &uintres) < 0)
st = ERROR;
}
else {
@@ -239,10 +216,10 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
break;
default:
debug_printf("not impl ");
-
+ st = ERROR;
}
- debug_printf("(%"PRIu64")\n", res1);
+ debug_printf("(%"PRIu64")\n", uintres);
buf ++;
c = *buf;
@@ -252,68 +229,97 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
return -1;
}
- switch (st) {
- case ZERO_OK:
- case DEC_POS_OK:
- case HEX_OK:
- case OCTAL_OK:
- case BIN_OK:
- if (nd.type == RTE_INT8 && res1 <= INT8_MAX) {
- if (res) *(int8_t *)res = (int8_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT16 && res1 <= INT16_MAX) {
- if (res) *(int16_t *)res = (int16_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT32 && res1 <= INT32_MAX) {
- if (res) *(int32_t *)res = (int32_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT64 && res1 <= INT64_MAX) {
- if (res) *(int64_t *)res = (int64_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT8 && res1 <= UINT8_MAX) {
- if (res) *(uint8_t *)res = (uint8_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT16 && res1 <= UINT16_MAX) {
- if (res) *(uint16_t *)res = (uint16_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT32 && res1 <= UINT32_MAX) {
- if (res) *(uint32_t *)res = (uint32_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT64) {
- if (res) *(uint64_t *)res = res1;
- return buf-srcbuf;
- } else {
- return -1;
- }
- break;
+ if (st != BIN_OK)
+ return -1;
+
+ /* was it negative? */
+ if (neg)
+ uintres = -uintres;
+
+ *res = uintres;
+ return buf - srcbuf;
+}
- case DEC_NEG_OK:
- if (nd.type == RTE_INT8 &&
- res1 <= INT8_MAX + 1) {
- if (res) *(int8_t *)res = (int8_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT16 &&
- res1 <= (uint16_t)INT16_MAX + 1) {
- if (res) *(int16_t *)res = (int16_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT32 &&
- res1 <= (uint32_t)INT32_MAX + 1) {
- if (res) *(int32_t *)res = (int32_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT64 &&
- res1 <= (uint64_t)INT64_MAX + 1) {
- if (res) *(int64_t *)res = (int64_t) (-res1);
- return buf-srcbuf;
- } else {
- return -1;
- }
+static int
+write_num(enum cmdline_numtype type, void *res, uint64_t uintres)
+{
+ switch (type) {
+ case RTE_UINT8:
+ *(uint8_t *)res = (uint8_t)uintres;
+ break;
+ case RTE_UINT16:
+ *(uint16_t *)res = (uint16_t)uintres;
+ break;
+ case RTE_UINT32:
+ *(uint32_t *)res = (uint32_t)uintres;
+ break;
+ case RTE_UINT64:
+ *(uint64_t *)res = uintres;
+ break;
+ case RTE_INT8:
+ *(int8_t *)res = (int8_t)uintres;
+ break;
+ case RTE_INT16:
+ *(int16_t *)res = (int16_t)uintres;
+ break;
+ case RTE_INT32:
+ *(int32_t *)res = (int32_t)uintres;
+ break;
+ case RTE_INT64:
+ *(int64_t *)res = (int64_t)uintres;
break;
default:
- debug_printf("error\n");
+ debug_printf("Wrong number type\n");
return -1;
}
+ return 0;
}
+/* parse an int */
+RTE_EXPORT_SYMBOL(cmdline_parse_num)
+int
+cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
+ unsigned ressize)
+{
+ struct cmdline_token_num_data nd;
+
+ if (!tk)
+ return -1;
+
+ if (!srcbuf || !*srcbuf)
+ return -1;
+
+ memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
+
+ /* check that we have enough room in res */
+ if (res && check_res_size(&nd, ressize) < 0)
+ return -1;
+
+ if (nd.type >= RTE_UINT8 && nd.type <= RTE_INT64) {
+ int ret, neg = *srcbuf == '-';
+ uint64_t uintres;
+
+ /* try parsing as number */
+ ret = parse_num(srcbuf, &uintres);
+
+ if (ret < 0) {
+ /* parse failed, try parsing as binary */
+ ret = parse_bin(srcbuf, &uintres);
+ if (ret < 0)
+ return -1;
+ }
+ /* check if we're within valid range */
+ if (check_parsed_num(nd.type, neg, uintres) < 0)
+ return -1;
+
+ /* parsing succeeded, write the value if necessary */
+ if (res && write_num(nd.type, res, uintres) < 0)
+ return -1;
+
+ return ret;
+ }
+ return -1;
+}
/* parse an int */
RTE_EXPORT_SYMBOL(cmdline_get_help_num)
@@ -328,9 +334,9 @@ cmdline_get_help_num(cmdline_parse_token_hdr_t *tk, char *dstbuf, unsigned int s
memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
- /* should not happen.... don't so this test */
- /* if (nd.type >= (sizeof(num_help)/sizeof(const char *))) */
- /* return -1; */
+ /* ensure we have a valid help message for the given type */
+ if (nd.type >= RTE_DIM(num_help))
+ return -1;
ret = strlcpy(dstbuf, num_help[nd.type], size);
if (ret < 0)
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v9 2/3] cmdline: add floating point support
2025-05-09 13:39 ` [PATCH v9 " Anatoly Burakov
@ 2025-05-09 13:39 ` Anatoly Burakov
2025-05-09 13:39 ` [PATCH v9 3/3] app/testpmd: add sleep command Anatoly Burakov
2025-05-09 13:42 ` [PATCH v9 1/3] cmdline: use C standard library as number parser Burakov, Anatoly
2 siblings, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-09 13:39 UTC (permalink / raw)
To: dev
Add support for parsing floating point numbers in cmdline library, as well
as unit tests for the new functionality. Use C library for parsing.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v7 -> v8:
- Fixed leftover comment about needing bigger epsilon value
- Fixed debug prints to avoid indexing num_help[] array
v6 -> v7:
- Fixed a bug in float compare in unit tests where a bigger epsilon
value than necessary was "needed" because we were comparing float
result to a double result
v5 -> v6:
- Small refactor to reduce amount of noise
- Use strtof for parsing single precision floats
- More unit tests
v4 -> v5:
- Reworked to use standard C library functions as much as possible,
keeping near-100% compatibility with earlier versions (the only
difference is that strings like "+4" are now considered valid)
v3 -> v4:
- Removed unnecessary check for integer overflow when parsing negative
floats (as we convert to double before changing sign)
- Make naming of float exponent states more consistent
v2 -> v3:
- Fixed a bug where a free-standing negative exponent ("1e-") would attempt to be
parsed, and added unit tests for this case
- Added support for floats in dpdk-cmdline-gen script
- Added documentation updates to call out float support
app/test/test_cmdline_num.c | 283 ++++++++++++++++++++++++-
buildtools/dpdk-cmdline-gen.py | 24 ++-
doc/guides/prog_guide/cmdline.rst | 3 +
doc/guides/rel_notes/release_25_07.rst | 5 +
lib/cmdline/cmdline_parse_num.c | 66 +++++-
lib/cmdline/cmdline_parse_num.h | 4 +-
6 files changed, 366 insertions(+), 19 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index 100c564188..832ed3d110 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -5,6 +5,8 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <rte_string_fns.h>
@@ -23,6 +25,11 @@ struct num_signed_str {
int64_t result;
};
+struct num_float_str {
+ const char * str;
+ double result;
+};
+
const struct num_unsigned_str num_valid_positive_strs[] = {
/* decimal positive */
{"0", 0 },
@@ -144,6 +151,63 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-0b1000000000000000000000000000000000000000000000000000000000000000", INT64_MIN },
};
+const struct num_float_str float_valid_strs[] = {
+ /* zero */
+ {"0", 0},
+ /* parse int as float */
+ {"1", 1},
+ {"-1", -1},
+ /* fractional */
+ {"1.23", 1.23},
+ {"-1.23", -1.23},
+ {"0.123", 0.123},
+ {"-0.123", -0.123},
+ {"123.456", 123.456},
+ {"-123.456", -123.456},
+ /* positive exponent */
+ {"1e2", 1e2},
+ {"-1e2", -1e2},
+ {"1E2", 1E2},
+ {"-1E2", -1E2},
+ {"0.12e3", 0.12e3},
+ {"-0.12e3", -0.12e3},
+ {"1.23e4", 1.23e4},
+ {"-1.23e4", -1.23e4},
+ {"1.23E4", 1.23E4},
+ {"-1.23E4", -1.23E4},
+ {"123.456e7", 123.456e7},
+ {"-123.456e7", -123.456e7},
+ {"123.456E7", 123.456E7},
+ {"-123.456E7", -123.456E7},
+ /* negative exponent */
+ {"1e-2", 1e-2},
+ {"-1e-2", -1e-2},
+ {"1E-2", 1E-2},
+ {"-1E-2", -1E-2},
+ {"0.12e-3", 0.12e-3},
+ {"-0.12e-3", -0.12e-3},
+ {"1.23e-4", 1.23e-4},
+ {"-1.23e-4", -1.23e-4},
+ {"1.23E-4", 1.23E-4},
+ {"-1.23E-4", -1.23E-4},
+ {"123.456e-7", 123.456e-7},
+ {"-123.456e-7", -123.456e-7},
+ {"123.456E-7", 123.456E-7},
+ {"-123.456E-7", -123.456E-7},
+ /* try overflowing float */
+ {"2e63", 2e63},
+ {"-2e63", -2e63},
+ {"2E63", 2E63},
+ {"-2E63", -2E63},
+ {"18446744073709551615", (double) UINT64_MAX},
+ {"-9223372036854775808", (double) INT64_MIN},
+ /* try overflowing double */
+ {"2e308", HUGE_VAL},
+ {"-2e308", -HUGE_VAL},
+ {"2E308", HUGE_VAL},
+ {"-2E308", HUGE_VAL},
+};
+
const struct num_unsigned_str num_garbage_positive_strs[] = {
/* valid strings with garbage on the end, should still be valid */
/* decimal */
@@ -214,6 +278,71 @@ const struct num_signed_str num_garbage_negative_strs[] = {
{"-01000000000000000000000 garbage", INT64_MIN },
};
+const struct num_float_str float_garbage_strs[] = {
+ /* valid strings with garbage on the end, should still be valid */
+ /* positive float positive exponent */
+ {"123.456e7\0garbage", 123.456e7},
+ {"123.456e7\rgarbage", 123.456e7},
+ {"123.456e7\tgarbage", 123.456e7},
+ {"123.456e7\ngarbage", 123.456e7},
+ {"123.456e7#garbage", 123.456e7},
+ {"123.456e7 garbage", 123.456e7},
+ /* negative float positive exponent */
+ {"-123.456e7\0garbage", -123.456e7},
+ {"-123.456e7\rgarbage", -123.456e7},
+ {"-123.456e7\tgarbage", -123.456e7},
+ {"-123.456e7\ngarbage", -123.456e7},
+ {"-123.456e7#garbage", -123.456e7},
+ {"-123.456e7 garbage", -123.456e7},
+ /* positive float negative exponent */
+ {"123.456e-7\0garbage", 123.456e-7},
+ {"123.456e-7\rgarbage", 123.456e-7},
+ {"123.456e-7\tgarbage", 123.456e-7},
+ {"123.456e-7\ngarbage", 123.456e-7},
+ {"123.456e-7#garbage", 123.456e-7},
+ {"123.456e-7 garbage", 123.456e-7},
+ /* negative float negative exponent */
+ {"-123.456e-7\0garbage", -123.456e-7},
+ {"-123.456e-7\rgarbage", -123.456e-7},
+ {"-123.456e-7\tgarbage", -123.456e-7},
+ {"-123.456e-7\ngarbage", -123.456e-7},
+ {"-123.456e-7#garbage", -123.456e-7},
+ {"-123.456e-7 garbage", -123.456e-7},
+ /* float overflows */
+ {"18446744073709551615\0garbage", (double) UINT64_MAX},
+ {"18446744073709551615\rgarbage", (double) UINT64_MAX},
+ {"18446744073709551615\ngarbage", (double) UINT64_MAX},
+ {"18446744073709551615\tgarbage", (double) UINT64_MAX},
+ {"18446744073709551615#garbage", (double) UINT64_MAX},
+ {"18446744073709551615 garbage", (double) UINT64_MAX},
+ {"-9223372036854775808\0garbage", (double) INT64_MIN},
+ {"-9223372036854775808\rgarbage", (double) INT64_MIN},
+ {"-9223372036854775808\ngarbage", (double) INT64_MIN},
+ {"-9223372036854775808\tgarbage", (double) INT64_MIN},
+ {"-9223372036854775808#garbage", (double) INT64_MIN},
+ {"-9223372036854775808 garbage", (double) INT64_MIN},
+};
+
+const char *float_invalid_strs[] = {
+ "1.1.",
+ "1.1.1",
+ "-1.1.",
+ "-1.1.1",
+ "e",
+ "1e",
+ "-1e",
+ "0.1e",
+ "-0.1e",
+ "1.e",
+ "-1.e",
+ "1.23e3.4",
+ "-1.23e3.4",
+ "1e1e",
+ "1e1e1",
+ "1e-",
+ "-1e-"
+};
+
const char * num_invalid_strs[] = {
"18446744073709551616", /* out of range unsigned */
"-9223372036854775809", /* out of range negative signed */
@@ -231,7 +360,16 @@ const char * num_invalid_strs[] = {
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
"1111000011110000111100001111000011110000111100001111000011110000"),
+ /* valid float values but should fail to parse as ints */
"1E3",
+ "-1E3",
+ "1.23",
+ "-1.23",
+ "1E-3",
+ "-1E-3",
+ "1.23E4",
+ "-1.23E4",
+ /* misc invalid values */
"0A",
"-B",
"1.23G",
@@ -244,6 +382,48 @@ const char * num_invalid_strs[] = {
"\0",
};
+static int
+float_cmp(double expected, void *actual_p, enum cmdline_numtype type)
+{
+ double eps;
+ double actual_d;
+
+ if (type == RTE_FLOAT_SINGLE) {
+ /* read as float, convert to double */
+ actual_d = (double)*(float *)actual_p;
+ /* downcast expected to float as well */
+ expected = (double)(float)expected;
+ eps = FLT_EPSILON;
+ } else {
+ /* read as double */
+ actual_d = *(double *)actual_p;
+ eps = DBL_EPSILON;
+ }
+ /* compare using epsilon value */
+ if (fabs(expected - actual_d) < eps)
+ return 0;
+ /* not equal */
+ return expected < actual_d ? -1 : 1;
+}
+
+static int
+can_parse_float(double expected_result, enum cmdline_numtype type)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ if (expected_result > FLT_MAX || expected_result < -FLT_MAX)
+ return 0;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (expected_result > DBL_MAX || expected_result < -DBL_MAX)
+ return 0;
+ break;
+ default:
+ return 1;
+ }
+ return 1;
+}
+
static int
can_parse_unsigned(uint64_t expected_result, enum cmdline_numtype type)
{
@@ -399,11 +579,11 @@ test_parse_num_invalid_data(void)
int ret = 0;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result; /* pick largest buffer */
cmdline_parse_token_num_t token;
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result; /* pick largest buffer */
token.num_data.type = type;
/* test full strings */
@@ -425,6 +605,31 @@ test_parse_num_invalid_data(void)
}
}
}
+
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result; /* pick largest buffer */
+ token.num_data.type = type;
+
+ /* test full strings */
+ for (i = 0; i < RTE_DIM(float_invalid_strs); i++) {
+
+ memset(&result, 0, sizeof(double));
+ memset(&buf, 0, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*)&token,
+ float_invalid_strs[i], (void*)&result, sizeof(result));
+ if (ret != -1) {
+ /* get some info about what we are trying to parse */
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ printf("Error: parsing %s as %s succeeded!\n",
+ float_invalid_strs[i], buf);
+ return -1;
+ }
+ }
+ }
return 0;
}
@@ -436,13 +641,13 @@ test_parse_num_valid(void)
enum cmdline_numtype type;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result;
cmdline_parse_token_num_t token;
/** valid strings **/
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive strings */
@@ -481,7 +686,7 @@ test_parse_num_valid(void)
cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
buf, sizeof(buf));
- ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
num_valid_negative_strs[i].str,
(void*)&result, sizeof(result));
@@ -517,10 +722,44 @@ test_parse_num_valid(void)
}
}
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(float_valid_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ float_valid_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(float_valid_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ float_valid_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(float_valid_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ float_valid_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
/** garbage strings **/
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive garbage strings */
@@ -598,6 +837,38 @@ test_parse_num_valid(void)
}
}
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(float_garbage_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ float_garbage_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(float_garbage_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ float_garbage_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(float_garbage_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ float_garbage_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
memset(&buf, 0, sizeof(buf));
/* coverage! */
diff --git a/buildtools/dpdk-cmdline-gen.py b/buildtools/dpdk-cmdline-gen.py
index 7dadded783..6c76d7116a 100755
--- a/buildtools/dpdk-cmdline-gen.py
+++ b/buildtools/dpdk-cmdline-gen.py
@@ -17,16 +17,18 @@
RTE_SET_USED(cl);
RTE_SET_USED(data);
"""
-NUMERIC_TYPES = [
- "UINT8",
- "UINT16",
- "UINT32",
- "UINT64",
- "INT8",
- "INT16",
- "INT32",
- "INT64",
-]
+NUMERIC_TYPES = {
+ "UINT8": "uint8_t",
+ "UINT16": "uint16_t",
+ "UINT32": "uint32_t",
+ "UINT64": "uint64_t",
+ "INT8": "int8_t",
+ "INT16": "int16_t",
+ "INT32": "int32_t",
+ "INT64": "int64_t",
+ "FLOAT_SINGLE": "float",
+ "FLOAT_DOUBLE": "double",
+}
def process_command(lineno, tokens, comment):
@@ -70,7 +72,7 @@ def process_command(lineno, tokens, comment):
f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});"
)
elif t_type in NUMERIC_TYPES:
- result_struct.append(f"\t{t_type.lower()}_t {t_name};")
+ result_struct.append(f"\t{NUMERIC_TYPES[t_type]} {t_name};")
initializers.append(
f"static cmdline_parse_token_num_t cmd_{name}_{t_name}_tok =\n"
f"\tTOKEN_NUM_INITIALIZER(struct cmd_{name}_result, {t_name}, RTE_{t_type});"
diff --git a/doc/guides/prog_guide/cmdline.rst b/doc/guides/prog_guide/cmdline.rst
index e20281ceb5..447a90e32e 100644
--- a/doc/guides/prog_guide/cmdline.rst
+++ b/doc/guides/prog_guide/cmdline.rst
@@ -22,6 +22,7 @@ The DPDK command-line library supports the following features:
* Strings
* Signed/unsigned 16/32/64-bit integers
+ * Single/double precision floats
* IP Addresses
* Ethernet Addresses
@@ -68,6 +69,8 @@ The format of the list file must be:
* ``<UINT16>port_id``
+ * ``<FLOAT_SINGLE>ratio``
+
* ``<IP>src_ip``
* ``<IPv4>dst_ip4``
diff --git a/doc/guides/rel_notes/release_25_07.rst b/doc/guides/rel_notes/release_25_07.rst
index 093b85d206..54bc545110 100644
--- a/doc/guides/rel_notes/release_25_07.rst
+++ b/doc/guides/rel_notes/release_25_07.rst
@@ -55,6 +55,11 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added floating point numbers support to cmdline library.**
+
+ The cmdline library now supports parsing single- and double-precision
+ floating point numbers in interactive user commands.
+
Removed Items
-------------
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index 8e5b75ff56..28b5cd0b28 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -8,6 +8,8 @@
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <string.h>
#include <eal_export.h>
#include <rte_string_fns.h>
@@ -34,6 +36,7 @@ struct cmdline_token_ops cmdline_token_num_ops = {
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
"INT8", "INT16", "INT32", "INT64",
+ "FLOAT_SINGLE", "FLOAT_DOUBLE"
};
static inline int
@@ -71,6 +74,14 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
if (ressize < sizeof(int64_t))
return -1;
break;
+ case RTE_FLOAT_SINGLE:
+ if (ressize < sizeof(float))
+ return -1;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (ressize < sizeof(double))
+ return -1;
+ break;
default:
debug_printf("Wrong number type: %d\n", nd->type);
return -1;
@@ -151,6 +162,26 @@ parse_num(const char *srcbuf, uint64_t *resptr)
return end - srcbuf;
}
+static int
+parse_float(const char *srcbuf, enum cmdline_numtype type, double *resptr)
+{
+ double dres;
+ char *end;
+
+ if (type == RTE_FLOAT_SINGLE)
+ dres = (double)strtof(srcbuf, &end);
+ else if (type == RTE_FLOAT_DOUBLE)
+ dres = strtod(srcbuf, &end);
+ else
+ return -1;
+
+ if (end == srcbuf || !cmdline_isendoftoken(*end) || isinf(dres))
+ return -1;
+
+ *resptr = dres;
+ return end - srcbuf;
+}
+
static int
parse_bin(const char *srcbuf, uint64_t *res)
{
@@ -275,7 +306,24 @@ write_num(enum cmdline_numtype type, void *res, uint64_t uintres)
return 0;
}
-/* parse an int */
+static int
+write_float(enum cmdline_numtype type, void *res, double dres)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ *(float *)res = (float)dres;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ *(double *)res = dres;
+ break;
+ default:
+ debug_printf("Wrong float type\n");
+ return -1;
+ }
+ return 0;
+}
+
+/* parse a number */
RTE_EXPORT_SYMBOL(cmdline_parse_num)
int
cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
@@ -295,6 +343,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
if (res && check_res_size(&nd, ressize) < 0)
return -1;
+ /* integer parsing */
if (nd.type >= RTE_UINT8 && nd.type <= RTE_INT64) {
int ret, neg = *srcbuf == '-';
uint64_t uintres;
@@ -316,6 +365,21 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
if (res && write_num(nd.type, res, uintres) < 0)
return -1;
+ return ret;
+ /* float parsing */
+ } else if (nd.type >= RTE_FLOAT_SINGLE && nd.type <= RTE_FLOAT_DOUBLE) {
+ double dres;
+ int ret;
+
+ /* try parsing as float */
+ ret = parse_float(srcbuf, nd.type, &dres);
+ if (ret < 0)
+ return -1;
+
+ /* parsing succeeded, write the value if necessary */
+ if (res && write_float(nd.type, res, dres))
+ return -1;
+
return ret;
}
return -1;
diff --git a/lib/cmdline/cmdline_parse_num.h b/lib/cmdline/cmdline_parse_num.h
index bdd0267612..b2792a2d11 100644
--- a/lib/cmdline/cmdline_parse_num.h
+++ b/lib/cmdline/cmdline_parse_num.h
@@ -22,7 +22,9 @@ enum cmdline_numtype {
RTE_INT8,
RTE_INT16,
RTE_INT32,
- RTE_INT64
+ RTE_INT64,
+ RTE_FLOAT_SINGLE,
+ RTE_FLOAT_DOUBLE,
};
struct cmdline_token_num_data {
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v9 3/3] app/testpmd: add sleep command
2025-05-09 13:39 ` [PATCH v9 " Anatoly Burakov
2025-05-09 13:39 ` [PATCH v9 2/3] cmdline: add floating point support Anatoly Burakov
@ 2025-05-09 13:39 ` Anatoly Burakov
2025-05-09 13:42 ` [PATCH v9 1/3] cmdline: use C standard library as number parser Burakov, Anatoly
2 siblings, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-09 13:39 UTC (permalink / raw)
To: dev, Aman Singh
Test-pmd already has a way to run a list of commands from file, but there
is no way to pause execution for a specified amount of time between two
commands. This may be necessary for simple automation, particularly for
waiting on some asynchronous operation such as link status update.
Add a simple sleep command to wait until certain number of seconds has
passed, using the newly added cmdline library floating point support.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v1 -> v2:
- Add floating point support to cmdline
- Use floating point format for pause command
app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index d4bb3ec998..b6152c07e6 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
"quit\n"
" Quit to prompt.\n\n"
+
+ "sleep secs\n"
+ " Sleep for secs seconds (can be fractional).\n\n"
);
}
@@ -7768,6 +7771,37 @@ static cmdline_parse_inst_t cmd_quit = {
},
};
+/* *** SLEEP *** */
+struct cmd_sleep_result {
+ cmdline_fixed_string_t sleep;
+ double secs;
+};
+
+static void cmd_sleep_parsed(void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_sleep_result *res = parsed_result;
+
+ rte_delay_us_sleep(res->secs * 1E6);
+}
+
+static cmdline_parse_token_string_t cmd_sleep_sleep =
+ TOKEN_STRING_INITIALIZER(struct cmd_sleep_result, sleep, "sleep");
+static cmdline_parse_token_num_t cmd_sleep_seconds =
+ TOKEN_NUM_INITIALIZER(struct cmd_sleep_result, secs, RTE_FLOAT_DOUBLE);
+
+static cmdline_parse_inst_t cmd_sleep = {
+ .f = cmd_sleep_parsed,
+ .data = NULL,
+ .help_str = "sleep <secs>: Sleep for a specified number of seconds",
+ .tokens = {
+ (void *)&cmd_sleep_sleep,
+ (void *)&cmd_sleep_seconds,
+ NULL,
+ },
+};
+
/* *** ADD/REMOVE MAC ADDRESS FROM A PORT *** */
struct cmd_mac_addr_result {
cmdline_fixed_string_t mac_addr_cmd;
@@ -13711,6 +13745,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_showdevice,
&cmd_showcfg,
&cmd_showfwdall,
+ &cmd_sleep,
&cmd_start,
&cmd_start_tx_first,
&cmd_start_tx_first_n,
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v9 1/3] cmdline: use C standard library as number parser
2025-05-09 13:39 ` [PATCH v9 " Anatoly Burakov
2025-05-09 13:39 ` [PATCH v9 2/3] cmdline: add floating point support Anatoly Burakov
2025-05-09 13:39 ` [PATCH v9 3/3] app/testpmd: add sleep command Anatoly Burakov
@ 2025-05-09 13:42 ` Burakov, Anatoly
2 siblings, 0 replies; 45+ messages in thread
From: Burakov, Anatoly @ 2025-05-09 13:42 UTC (permalink / raw)
To: dev
On 5/9/2025 3:39 PM, Anatoly Burakov wrote:
> Remove custom number parser and use C standard library instead. In order to
> keep compatibility with earlier versions of the parser, we have to take
> into account a couple of quirks:
>
> - We did not consider "negative" numbers to be valid for anything other
> than base-10 numbers, whereas C standard library does. Adjust the tests
> to match the new behavior.
> - We did not consider numbers such as "+4" to be valid, whereas C
> standard library does. Adjust the tests to match the new behavior.
> - C standard library's strtoull does not do range checks on negative
> numbers, so we have to parse knowingly-negative numbers as signed.
<snip>
> - C standard library does not support binary numbers, so we keep around the
> relevant parts of the custom parser in place to support binary numbers.
Missed updating this part. Better wording:
cmdline: use C standard library as number parser
- Some C standard library versions do not support binary numbers, so we
keep around the relevant parts of the custom parser in place to support
binary numbers. However, since those libc versions that do support them,
also support them being negative, allow negative binary as well.
--
Thanks,
Anatoly
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v10 1/3] cmdline: use C standard library as number parser
2025-05-02 12:27 [PATCH v1 1/1] app/testpmd: add sleep command Anatoly Burakov
` (9 preceding siblings ...)
2025-05-09 13:39 ` [PATCH v9 " Anatoly Burakov
@ 2025-05-09 14:41 ` Anatoly Burakov
2025-05-09 14:41 ` [PATCH v10 2/3] cmdline: add floating point support Anatoly Burakov
2025-05-09 14:41 ` [PATCH v10 3/3] app/testpmd: add sleep command Anatoly Burakov
10 siblings, 2 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-09 14:41 UTC (permalink / raw)
To: dev
Remove custom number parser and use C standard library instead. In order to
keep compatibility with earlier versions of the parser, we have to take
into account a couple of quirks:
- We did not consider "negative" numbers to be valid for anything other
than base-10 numbers, whereas C standard library does. Adjust the tests
to match the new behavior.
- We did not consider numbers such as "+4" to be valid, whereas C
standard library does. Adjust the tests to match the new behavior.
- C standard library's strtoull does not do range checks on negative
numbers, so we have to parse knowingly-negative numbers as signed.
- Some C standard library versions do not support binary numbers, so we
keep around the relevant parts of the custom parser in place to support
them. However, since those libc versions that do support them also
support them being negative, allow negative binary in our parser as well.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v9 -> v10:
- Fixed commit message not reflecting changes for v9
- Reworked enum range check to avoid compile issues on some platforms
v8 -> v9:
- glibc 2.38 supports binary formats (0bxxxx and -0bxxxx) under certain conditions,
so in order to ensure the same functionality on all glic versions, add support for
positive and negative binary formats, and adjust tests accordingly
v7 -> v8:
- Added the commented-out out-of-bounds check back
- Replaced debug print messages to ensure they don't attempt to
index the num_help[] array (should fix compile errors)
v5 -> v6:
- Allowed more negative numbers (such as negative octals or hex)
- Updated unit tests to check new cases
- Small refactoring of code to reduce amount of noise
- More verbose debug output
v4 -> v5:
- Added this commit
app/test/test_cmdline_num.c | 34 ++-
lib/cmdline/cmdline_parse_num.c | 395 +++++++++++++++++---------------
2 files changed, 238 insertions(+), 191 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index 9276de59bd..100c564188 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -139,6 +139,9 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-2147483648", INT32_MIN },
{"-2147483649", INT32_MIN - 1LL },
{"-9223372036854775808", INT64_MIN },
+ {"-0x8000000000000000", INT64_MIN },
+ {"-01000000000000000000000", INT64_MIN },
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000", INT64_MIN },
};
const struct num_unsigned_str num_garbage_positive_strs[] = {
@@ -175,12 +178,40 @@ const struct num_unsigned_str num_garbage_positive_strs[] = {
const struct num_signed_str num_garbage_negative_strs[] = {
/* valid strings with garbage on the end, should still be valid */
+ /* negative numbers */
{"-9223372036854775808\0garbage", INT64_MIN },
{"-9223372036854775808\rgarbage", INT64_MIN },
{"-9223372036854775808\tgarbage", INT64_MIN },
{"-9223372036854775808\ngarbage", INT64_MIN },
{"-9223372036854775808#garbage", INT64_MIN },
{"-9223372036854775808 garbage", INT64_MIN },
+ /* negative hex */
+ {"-0x8000000000000000\0garbage", INT64_MIN },
+ {"-0x8000000000000000\rgarbage", INT64_MIN },
+ {"-0x8000000000000000\tgarbage", INT64_MIN },
+ {"-0x8000000000000000\ngarbage", INT64_MIN },
+ {"-0x8000000000000000#garbage", INT64_MIN },
+ {"-0x8000000000000000 garbage", INT64_MIN },
+ /* negative binary */
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000\0garbage",
+ INT64_MIN },
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000\rgarbage",
+ INT64_MIN },
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000\tgarbage",
+ INT64_MIN },
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000\ngarbage",
+ INT64_MIN },
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000#garbage",
+ INT64_MIN },
+ {"-0b1000000000000000000000000000000000000000000000000000000000000000 garbage",
+ INT64_MIN },
+ /* negative octal */
+ {"-01000000000000000000000\0garbage", INT64_MIN },
+ {"-01000000000000000000000\rgarbage", INT64_MIN },
+ {"-01000000000000000000000\tgarbage", INT64_MIN },
+ {"-01000000000000000000000\ngarbage", INT64_MIN },
+ {"-01000000000000000000000#garbage", INT64_MIN },
+ {"-01000000000000000000000 garbage", INT64_MIN },
};
const char * num_invalid_strs[] = {
@@ -197,15 +228,12 @@ const char * num_invalid_strs[] = {
"0b01110101017001",
/* false negative numbers */
"-12345F623",
- "-0x1234580A",
- "-0b0111010101",
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
"1111000011110000111100001111000011110000111100001111000011110000"),
"1E3",
"0A",
"-B",
- "+4",
"1.23G",
"",
" ",
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index f21796bedb..88435d069e 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -4,6 +4,7 @@
* All rights reserved.
*/
+#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
@@ -29,23 +30,6 @@ struct cmdline_token_ops cmdline_token_num_ops = {
.get_help = cmdline_get_help_num,
};
-enum num_parse_state_t {
- START,
- DEC_NEG,
- BIN,
- HEX,
-
- ERROR,
-
- FIRST_OK, /* not used */
- ZERO_OK,
- HEX_OK,
- OCTAL_OK,
- BIN_OK,
- DEC_NEG_OK,
- DEC_POS_OK,
-};
-
/* Keep it sync with enum in .h */
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
@@ -53,13 +37,13 @@ static const char * num_help[] = {
};
static inline int
-add_to_res(unsigned int c, uint64_t *res, unsigned int base)
+add_to_bin(unsigned int c, uint64_t *res)
{
/* overflow */
- if ((UINT64_MAX - c) / base < *res)
+ if ((UINT64_MAX - c) / 2 < *res)
return -1;
- *res = (uint64_t) (*res * base + c);
+ *res = (uint64_t) (*res * 2 + c);
return 0;
}
@@ -88,138 +72,142 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
return -1;
break;
default:
+ debug_printf("Wrong number type: %d\n", nd->type);
return -1;
}
return 0;
}
-/* parse an int */
-RTE_EXPORT_SYMBOL(cmdline_parse_num)
-int
-cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
- unsigned ressize)
+static int
+validate_type(enum cmdline_numtype type)
{
- struct cmdline_token_num_data nd;
- enum num_parse_state_t st = START;
+ if (type < RTE_UINT8 || type > RTE_INT64)
+ return -1;
+ /* ensure no buffer overrun can occur */
+ if ((uint64_t) type >= RTE_DIM(num_help))
+ return -1;
+ return 0;
+}
+
+static int
+check_parsed_num(enum cmdline_numtype type, int neg, uint64_t uintres)
+{
+ int lo_ok, hi_ok;
+
+ switch (type) {
+ case RTE_UINT8:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT8_MAX;
+ break;
+ case RTE_UINT16:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT16_MAX;
+ break;
+ case RTE_UINT32:
+ lo_ok = !neg;
+ hi_ok = uintres <= UINT32_MAX;
+ break;
+ case RTE_UINT64:
+ lo_ok = !neg;
+ hi_ok = 1; /* can't be out of range if parsed successfully */
+ break;
+ case RTE_INT8:
+ lo_ok = !neg || (int64_t)uintres >= INT8_MIN;
+ hi_ok = neg || uintres <= INT8_MAX;
+ break;
+ case RTE_INT16:
+ lo_ok = !neg || (int64_t)uintres >= INT16_MIN;
+ hi_ok = neg || uintres <= INT16_MAX;
+ break;
+ case RTE_INT32:
+ lo_ok = !neg || (int64_t)uintres >= INT32_MIN;
+ hi_ok = neg || uintres <= INT32_MAX;
+ break;
+ case RTE_INT64:
+ lo_ok = 1; /* can't be out of range if parsed successfully */
+ hi_ok = neg || uintres <= INT64_MAX;
+ break;
+ default:
+ debug_printf("Wrong number type\n");
+ return -1;
+ }
+ /* check ranges */
+ if (!lo_ok || !hi_ok)
+ return -1;
+ return 0;
+}
+
+static int
+parse_num(const char *srcbuf, uint64_t *resptr)
+{
+ uint64_t uintres;
+ char *end;
+ int neg = *srcbuf == '-';
+
+ /*
+ * strtoull does not do range checks on negative numbers, so we need to
+ * use strtoll if we know the value we're parsing looks like a negative
+ * one. we use base 0 for both, 0 means autodetect base.
+ */
+ errno = 0;
+ if (neg)
+ uintres = (uint64_t)strtoll(srcbuf, &end, 0);
+ else
+ uintres = strtoull(srcbuf, &end, 0);
+
+ if (end == srcbuf || !cmdline_isendoftoken(*end) || errno == ERANGE)
+ return -1;
+
+ *resptr = uintres;
+ return end - srcbuf;
+}
+
+static int
+parse_bin(const char *srcbuf, uint64_t *res)
+{
+ uint64_t uintres = 0;
+ enum {
+ ERROR,
+ START,
+ BIN,
+ NEG,
+ ZERO_OK,
+ BIN_OK,
+ } st = START;
const char * buf;
char c;
- uint64_t res1 = 0;
-
- if (!tk)
- return -1;
-
- if (!srcbuf || !*srcbuf)
- return -1;
+ int neg = 0;
buf = srcbuf;
c = *buf;
-
- memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
-
- /* check that we have enough room in res */
- if (res) {
- if (check_res_size(&nd, ressize) < 0)
- return -1;
- }
-
while (st != ERROR && c && !cmdline_isendoftoken(c)) {
debug_printf("%c %x -> ", c, c);
switch (st) {
case START:
- if (c == '-') {
- st = DEC_NEG;
- }
- else if (c == '0') {
+ if (c == '0') {
st = ZERO_OK;
+ } else if (c == '-') {
+ neg = 1;
+ st = NEG;
+ }
+ else {
+ st = ERROR;
}
- else if (c >= '1' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = DEC_POS_OK;
+ break;
+
+ case NEG:
+ if (c == '0') {
+ st = ZERO_OK;
}
- else {
+ else {
st = ERROR;
}
break;
case ZERO_OK:
- if (c == 'x') {
- st = HEX;
- }
- else if (c == 'b') {
+ if (c == 'b') {
st = BIN;
}
- else if (c >= '0' && c <= '7') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = OCTAL_OK;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_NEG:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- else
- st = DEC_NEG_OK;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_NEG_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
- case DEC_POS_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 10) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
- case HEX:
- st = HEX_OK;
- /* fall-through */
- case HEX_OK:
- if (c >= '0' && c <= '9') {
- if (add_to_res(c - '0', &res1, 16) < 0)
- st = ERROR;
- }
- else if (c >= 'a' && c <= 'f') {
- if (add_to_res(c - 'a' + 10, &res1, 16) < 0)
- st = ERROR;
- }
- else if (c >= 'A' && c <= 'F') {
- if (add_to_res(c - 'A' + 10, &res1, 16) < 0)
- st = ERROR;
- }
- else {
- st = ERROR;
- }
- break;
-
-
- case OCTAL_OK:
- if (c >= '0' && c <= '7') {
- if (add_to_res(c - '0', &res1, 8) < 0)
- st = ERROR;
- }
else {
st = ERROR;
}
@@ -230,7 +218,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
/* fall-through */
case BIN_OK:
if (c >= '0' && c <= '1') {
- if (add_to_res(c - '0', &res1, 2) < 0)
+ if (add_to_bin(c - '0', &uintres) < 0)
st = ERROR;
}
else {
@@ -239,10 +227,10 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
break;
default:
debug_printf("not impl ");
-
+ st = ERROR;
}
- debug_printf("(%"PRIu64")\n", res1);
+ debug_printf("(%"PRIu64")\n", uintres);
buf ++;
c = *buf;
@@ -252,68 +240,100 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
return -1;
}
- switch (st) {
- case ZERO_OK:
- case DEC_POS_OK:
- case HEX_OK:
- case OCTAL_OK:
- case BIN_OK:
- if (nd.type == RTE_INT8 && res1 <= INT8_MAX) {
- if (res) *(int8_t *)res = (int8_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT16 && res1 <= INT16_MAX) {
- if (res) *(int16_t *)res = (int16_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT32 && res1 <= INT32_MAX) {
- if (res) *(int32_t *)res = (int32_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_INT64 && res1 <= INT64_MAX) {
- if (res) *(int64_t *)res = (int64_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT8 && res1 <= UINT8_MAX) {
- if (res) *(uint8_t *)res = (uint8_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT16 && res1 <= UINT16_MAX) {
- if (res) *(uint16_t *)res = (uint16_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT32 && res1 <= UINT32_MAX) {
- if (res) *(uint32_t *)res = (uint32_t) res1;
- return buf-srcbuf;
- } else if (nd.type == RTE_UINT64) {
- if (res) *(uint64_t *)res = res1;
- return buf-srcbuf;
- } else {
- return -1;
- }
- break;
+ if (st != BIN_OK)
+ return -1;
+
+ /* was it negative? */
+ if (neg)
+ uintres = -uintres;
+
+ *res = uintres;
+ return buf - srcbuf;
+}
- case DEC_NEG_OK:
- if (nd.type == RTE_INT8 &&
- res1 <= INT8_MAX + 1) {
- if (res) *(int8_t *)res = (int8_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT16 &&
- res1 <= (uint16_t)INT16_MAX + 1) {
- if (res) *(int16_t *)res = (int16_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT32 &&
- res1 <= (uint32_t)INT32_MAX + 1) {
- if (res) *(int32_t *)res = (int32_t) (-res1);
- return buf-srcbuf;
- } else if (nd.type == RTE_INT64 &&
- res1 <= (uint64_t)INT64_MAX + 1) {
- if (res) *(int64_t *)res = (int64_t) (-res1);
- return buf-srcbuf;
- } else {
- return -1;
- }
+static int
+write_num(enum cmdline_numtype type, void *res, uint64_t uintres)
+{
+ switch (type) {
+ case RTE_UINT8:
+ *(uint8_t *)res = (uint8_t)uintres;
+ break;
+ case RTE_UINT16:
+ *(uint16_t *)res = (uint16_t)uintres;
+ break;
+ case RTE_UINT32:
+ *(uint32_t *)res = (uint32_t)uintres;
+ break;
+ case RTE_UINT64:
+ *(uint64_t *)res = uintres;
+ break;
+ case RTE_INT8:
+ *(int8_t *)res = (int8_t)uintres;
+ break;
+ case RTE_INT16:
+ *(int16_t *)res = (int16_t)uintres;
+ break;
+ case RTE_INT32:
+ *(int32_t *)res = (int32_t)uintres;
+ break;
+ case RTE_INT64:
+ *(int64_t *)res = (int64_t)uintres;
break;
default:
- debug_printf("error\n");
+ debug_printf("Wrong number type\n");
return -1;
}
+ return 0;
}
+/* parse an int */
+RTE_EXPORT_SYMBOL(cmdline_parse_num)
+int
+cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
+ unsigned ressize)
+{
+ struct cmdline_token_num_data nd;
+
+ if (!tk)
+ return -1;
+
+ if (!srcbuf || !*srcbuf)
+ return -1;
+
+ memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
+
+ if (validate_type(nd.type) < 0)
+ return -1;
+
+ /* check that we have enough room in res */
+ if (res && check_res_size(&nd, ressize) < 0)
+ return -1;
+
+ if (nd.type >= RTE_UINT8 && nd.type <= RTE_INT64) {
+ int ret, neg = *srcbuf == '-';
+ uint64_t uintres;
+
+ /* try parsing as number */
+ ret = parse_num(srcbuf, &uintres);
+
+ if (ret < 0) {
+ /* parse failed, try parsing as binary */
+ ret = parse_bin(srcbuf, &uintres);
+ if (ret < 0)
+ return -1;
+ }
+ /* check if we're within valid range */
+ if (check_parsed_num(nd.type, neg, uintres) < 0)
+ return -1;
+
+ /* parsing succeeded, write the value if necessary */
+ if (res && write_num(nd.type, res, uintres) < 0)
+ return -1;
+
+ return ret;
+ }
+ return -1;
+}
/* parse an int */
RTE_EXPORT_SYMBOL(cmdline_get_help_num)
@@ -328,9 +348,8 @@ cmdline_get_help_num(cmdline_parse_token_hdr_t *tk, char *dstbuf, unsigned int s
memcpy(&nd, &((struct cmdline_token_num *)tk)->num_data, sizeof(nd));
- /* should not happen.... don't so this test */
- /* if (nd.type >= (sizeof(num_help)/sizeof(const char *))) */
- /* return -1; */
+ if (validate_type(nd.type) < 0)
+ return -1;
ret = strlcpy(dstbuf, num_help[nd.type], size);
if (ret < 0)
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v10 2/3] cmdline: add floating point support
2025-05-09 14:41 ` [PATCH v10 " Anatoly Burakov
@ 2025-05-09 14:41 ` Anatoly Burakov
2025-05-09 14:41 ` [PATCH v10 3/3] app/testpmd: add sleep command Anatoly Burakov
1 sibling, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-09 14:41 UTC (permalink / raw)
To: dev
Add support for parsing floating point numbers in cmdline library, as well
as unit tests for the new functionality. Use C library for parsing.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v7 -> v8:
- Fixed leftover comment about needing bigger epsilon value
- Fixed debug prints to avoid indexing num_help[] array
v6 -> v7:
- Fixed a bug in float compare in unit tests where a bigger epsilon
value than necessary was "needed" because we were comparing float
result to a double result
v5 -> v6:
- Small refactor to reduce amount of noise
- Use strtof for parsing single precision floats
- More unit tests
v4 -> v5:
- Reworked to use standard C library functions as much as possible,
keeping near-100% compatibility with earlier versions (the only
difference is that strings like "+4" are now considered valid)
v3 -> v4:
- Removed unnecessary check for integer overflow when parsing negative
floats (as we convert to double before changing sign)
- Make naming of float exponent states more consistent
v2 -> v3:
- Fixed a bug where a free-standing negative exponent ("1e-") would attempt to be
parsed, and added unit tests for this case
- Added support for floats in dpdk-cmdline-gen script
- Added documentation updates to call out float support
app/test/test_cmdline_num.c | 283 ++++++++++++++++++++++++-
buildtools/dpdk-cmdline-gen.py | 24 ++-
doc/guides/prog_guide/cmdline.rst | 3 +
doc/guides/rel_notes/release_25_07.rst | 5 +
lib/cmdline/cmdline_parse_num.c | 68 +++++-
lib/cmdline/cmdline_parse_num.h | 4 +-
6 files changed, 367 insertions(+), 20 deletions(-)
diff --git a/app/test/test_cmdline_num.c b/app/test/test_cmdline_num.c
index 100c564188..832ed3d110 100644
--- a/app/test/test_cmdline_num.c
+++ b/app/test/test_cmdline_num.c
@@ -5,6 +5,8 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <rte_string_fns.h>
@@ -23,6 +25,11 @@ struct num_signed_str {
int64_t result;
};
+struct num_float_str {
+ const char * str;
+ double result;
+};
+
const struct num_unsigned_str num_valid_positive_strs[] = {
/* decimal positive */
{"0", 0 },
@@ -144,6 +151,63 @@ const struct num_signed_str num_valid_negative_strs[] = {
{"-0b1000000000000000000000000000000000000000000000000000000000000000", INT64_MIN },
};
+const struct num_float_str float_valid_strs[] = {
+ /* zero */
+ {"0", 0},
+ /* parse int as float */
+ {"1", 1},
+ {"-1", -1},
+ /* fractional */
+ {"1.23", 1.23},
+ {"-1.23", -1.23},
+ {"0.123", 0.123},
+ {"-0.123", -0.123},
+ {"123.456", 123.456},
+ {"-123.456", -123.456},
+ /* positive exponent */
+ {"1e2", 1e2},
+ {"-1e2", -1e2},
+ {"1E2", 1E2},
+ {"-1E2", -1E2},
+ {"0.12e3", 0.12e3},
+ {"-0.12e3", -0.12e3},
+ {"1.23e4", 1.23e4},
+ {"-1.23e4", -1.23e4},
+ {"1.23E4", 1.23E4},
+ {"-1.23E4", -1.23E4},
+ {"123.456e7", 123.456e7},
+ {"-123.456e7", -123.456e7},
+ {"123.456E7", 123.456E7},
+ {"-123.456E7", -123.456E7},
+ /* negative exponent */
+ {"1e-2", 1e-2},
+ {"-1e-2", -1e-2},
+ {"1E-2", 1E-2},
+ {"-1E-2", -1E-2},
+ {"0.12e-3", 0.12e-3},
+ {"-0.12e-3", -0.12e-3},
+ {"1.23e-4", 1.23e-4},
+ {"-1.23e-4", -1.23e-4},
+ {"1.23E-4", 1.23E-4},
+ {"-1.23E-4", -1.23E-4},
+ {"123.456e-7", 123.456e-7},
+ {"-123.456e-7", -123.456e-7},
+ {"123.456E-7", 123.456E-7},
+ {"-123.456E-7", -123.456E-7},
+ /* try overflowing float */
+ {"2e63", 2e63},
+ {"-2e63", -2e63},
+ {"2E63", 2E63},
+ {"-2E63", -2E63},
+ {"18446744073709551615", (double) UINT64_MAX},
+ {"-9223372036854775808", (double) INT64_MIN},
+ /* try overflowing double */
+ {"2e308", HUGE_VAL},
+ {"-2e308", -HUGE_VAL},
+ {"2E308", HUGE_VAL},
+ {"-2E308", HUGE_VAL},
+};
+
const struct num_unsigned_str num_garbage_positive_strs[] = {
/* valid strings with garbage on the end, should still be valid */
/* decimal */
@@ -214,6 +278,71 @@ const struct num_signed_str num_garbage_negative_strs[] = {
{"-01000000000000000000000 garbage", INT64_MIN },
};
+const struct num_float_str float_garbage_strs[] = {
+ /* valid strings with garbage on the end, should still be valid */
+ /* positive float positive exponent */
+ {"123.456e7\0garbage", 123.456e7},
+ {"123.456e7\rgarbage", 123.456e7},
+ {"123.456e7\tgarbage", 123.456e7},
+ {"123.456e7\ngarbage", 123.456e7},
+ {"123.456e7#garbage", 123.456e7},
+ {"123.456e7 garbage", 123.456e7},
+ /* negative float positive exponent */
+ {"-123.456e7\0garbage", -123.456e7},
+ {"-123.456e7\rgarbage", -123.456e7},
+ {"-123.456e7\tgarbage", -123.456e7},
+ {"-123.456e7\ngarbage", -123.456e7},
+ {"-123.456e7#garbage", -123.456e7},
+ {"-123.456e7 garbage", -123.456e7},
+ /* positive float negative exponent */
+ {"123.456e-7\0garbage", 123.456e-7},
+ {"123.456e-7\rgarbage", 123.456e-7},
+ {"123.456e-7\tgarbage", 123.456e-7},
+ {"123.456e-7\ngarbage", 123.456e-7},
+ {"123.456e-7#garbage", 123.456e-7},
+ {"123.456e-7 garbage", 123.456e-7},
+ /* negative float negative exponent */
+ {"-123.456e-7\0garbage", -123.456e-7},
+ {"-123.456e-7\rgarbage", -123.456e-7},
+ {"-123.456e-7\tgarbage", -123.456e-7},
+ {"-123.456e-7\ngarbage", -123.456e-7},
+ {"-123.456e-7#garbage", -123.456e-7},
+ {"-123.456e-7 garbage", -123.456e-7},
+ /* float overflows */
+ {"18446744073709551615\0garbage", (double) UINT64_MAX},
+ {"18446744073709551615\rgarbage", (double) UINT64_MAX},
+ {"18446744073709551615\ngarbage", (double) UINT64_MAX},
+ {"18446744073709551615\tgarbage", (double) UINT64_MAX},
+ {"18446744073709551615#garbage", (double) UINT64_MAX},
+ {"18446744073709551615 garbage", (double) UINT64_MAX},
+ {"-9223372036854775808\0garbage", (double) INT64_MIN},
+ {"-9223372036854775808\rgarbage", (double) INT64_MIN},
+ {"-9223372036854775808\ngarbage", (double) INT64_MIN},
+ {"-9223372036854775808\tgarbage", (double) INT64_MIN},
+ {"-9223372036854775808#garbage", (double) INT64_MIN},
+ {"-9223372036854775808 garbage", (double) INT64_MIN},
+};
+
+const char *float_invalid_strs[] = {
+ "1.1.",
+ "1.1.1",
+ "-1.1.",
+ "-1.1.1",
+ "e",
+ "1e",
+ "-1e",
+ "0.1e",
+ "-0.1e",
+ "1.e",
+ "-1.e",
+ "1.23e3.4",
+ "-1.23e3.4",
+ "1e1e",
+ "1e1e1",
+ "1e-",
+ "-1e-"
+};
+
const char * num_invalid_strs[] = {
"18446744073709551616", /* out of range unsigned */
"-9223372036854775809", /* out of range negative signed */
@@ -231,7 +360,16 @@ const char * num_invalid_strs[] = {
/* too long (128+ chars) */
("0b1111000011110000111100001111000011110000111100001111000011110000"
"1111000011110000111100001111000011110000111100001111000011110000"),
+ /* valid float values but should fail to parse as ints */
"1E3",
+ "-1E3",
+ "1.23",
+ "-1.23",
+ "1E-3",
+ "-1E-3",
+ "1.23E4",
+ "-1.23E4",
+ /* misc invalid values */
"0A",
"-B",
"1.23G",
@@ -244,6 +382,48 @@ const char * num_invalid_strs[] = {
"\0",
};
+static int
+float_cmp(double expected, void *actual_p, enum cmdline_numtype type)
+{
+ double eps;
+ double actual_d;
+
+ if (type == RTE_FLOAT_SINGLE) {
+ /* read as float, convert to double */
+ actual_d = (double)*(float *)actual_p;
+ /* downcast expected to float as well */
+ expected = (double)(float)expected;
+ eps = FLT_EPSILON;
+ } else {
+ /* read as double */
+ actual_d = *(double *)actual_p;
+ eps = DBL_EPSILON;
+ }
+ /* compare using epsilon value */
+ if (fabs(expected - actual_d) < eps)
+ return 0;
+ /* not equal */
+ return expected < actual_d ? -1 : 1;
+}
+
+static int
+can_parse_float(double expected_result, enum cmdline_numtype type)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ if (expected_result > FLT_MAX || expected_result < -FLT_MAX)
+ return 0;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (expected_result > DBL_MAX || expected_result < -DBL_MAX)
+ return 0;
+ break;
+ default:
+ return 1;
+ }
+ return 1;
+}
+
static int
can_parse_unsigned(uint64_t expected_result, enum cmdline_numtype type)
{
@@ -399,11 +579,11 @@ test_parse_num_invalid_data(void)
int ret = 0;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result; /* pick largest buffer */
cmdline_parse_token_num_t token;
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result; /* pick largest buffer */
token.num_data.type = type;
/* test full strings */
@@ -425,6 +605,31 @@ test_parse_num_invalid_data(void)
}
}
}
+
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result; /* pick largest buffer */
+ token.num_data.type = type;
+
+ /* test full strings */
+ for (i = 0; i < RTE_DIM(float_invalid_strs); i++) {
+
+ memset(&result, 0, sizeof(double));
+ memset(&buf, 0, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*)&token,
+ float_invalid_strs[i], (void*)&result, sizeof(result));
+ if (ret != -1) {
+ /* get some info about what we are trying to parse */
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ printf("Error: parsing %s as %s succeeded!\n",
+ float_invalid_strs[i], buf);
+ return -1;
+ }
+ }
+ }
return 0;
}
@@ -436,13 +641,13 @@ test_parse_num_valid(void)
enum cmdline_numtype type;
unsigned i;
char buf[CMDLINE_TEST_BUFSIZE];
- uint64_t result;
cmdline_parse_token_num_t token;
/** valid strings **/
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive strings */
@@ -481,7 +686,7 @@ test_parse_num_valid(void)
cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
buf, sizeof(buf));
- ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
num_valid_negative_strs[i].str,
(void*)&result, sizeof(result));
@@ -517,10 +722,44 @@ test_parse_num_valid(void)
}
}
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(float_valid_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ float_valid_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(float_valid_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ float_valid_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(float_valid_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ float_valid_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
/** garbage strings **/
- /* cycle through all possible parsed types */
+ /* cycle through all possible integer types */
for (type = RTE_UINT8; type <= RTE_INT64; type++) {
+ uint64_t result;
token.num_data.type = type;
/* test positive garbage strings */
@@ -598,6 +837,38 @@ test_parse_num_valid(void)
}
}
+ /* cycle through all possible float types */
+ for (type = RTE_FLOAT_SINGLE; type <= RTE_FLOAT_DOUBLE; type++) {
+ double result;
+ token.num_data.type = type;
+
+ /* test all valid strings */
+ for (i = 0; i < RTE_DIM(float_garbage_strs); i++) {
+ result = 0;
+ memset(&buf, 0, sizeof(buf));
+
+ cmdline_get_help_num((cmdline_parse_token_hdr_t*)&token,
+ buf, sizeof(buf));
+
+ ret = cmdline_parse_num((cmdline_parse_token_hdr_t*) &token,
+ float_garbage_strs[i].str,
+ (void*)&result, sizeof(result));
+
+ /* if it should have passed but didn't, or if it should have failed but didn't */
+ if ((ret < 0) == (can_parse_float(float_garbage_strs[i].result, type) > 0)) {
+ printf("Error: parser behaves unexpectedly when parsing %s as %s!\n",
+ float_garbage_strs[i].str, buf);
+ return -1;
+ }
+ /* check if result matches */
+ if (ret > 0 && float_cmp(float_garbage_strs[i].result, &result, type) != 0) {
+ printf("Error: parsing %s as %s failed: result mismatch!\n",
+ float_garbage_strs[i].str, buf);
+ return -1;
+ }
+ }
+ }
+
memset(&buf, 0, sizeof(buf));
/* coverage! */
diff --git a/buildtools/dpdk-cmdline-gen.py b/buildtools/dpdk-cmdline-gen.py
index 7dadded783..6c76d7116a 100755
--- a/buildtools/dpdk-cmdline-gen.py
+++ b/buildtools/dpdk-cmdline-gen.py
@@ -17,16 +17,18 @@
RTE_SET_USED(cl);
RTE_SET_USED(data);
"""
-NUMERIC_TYPES = [
- "UINT8",
- "UINT16",
- "UINT32",
- "UINT64",
- "INT8",
- "INT16",
- "INT32",
- "INT64",
-]
+NUMERIC_TYPES = {
+ "UINT8": "uint8_t",
+ "UINT16": "uint16_t",
+ "UINT32": "uint32_t",
+ "UINT64": "uint64_t",
+ "INT8": "int8_t",
+ "INT16": "int16_t",
+ "INT32": "int32_t",
+ "INT64": "int64_t",
+ "FLOAT_SINGLE": "float",
+ "FLOAT_DOUBLE": "double",
+}
def process_command(lineno, tokens, comment):
@@ -70,7 +72,7 @@ def process_command(lineno, tokens, comment):
f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});"
)
elif t_type in NUMERIC_TYPES:
- result_struct.append(f"\t{t_type.lower()}_t {t_name};")
+ result_struct.append(f"\t{NUMERIC_TYPES[t_type]} {t_name};")
initializers.append(
f"static cmdline_parse_token_num_t cmd_{name}_{t_name}_tok =\n"
f"\tTOKEN_NUM_INITIALIZER(struct cmd_{name}_result, {t_name}, RTE_{t_type});"
diff --git a/doc/guides/prog_guide/cmdline.rst b/doc/guides/prog_guide/cmdline.rst
index e20281ceb5..447a90e32e 100644
--- a/doc/guides/prog_guide/cmdline.rst
+++ b/doc/guides/prog_guide/cmdline.rst
@@ -22,6 +22,7 @@ The DPDK command-line library supports the following features:
* Strings
* Signed/unsigned 16/32/64-bit integers
+ * Single/double precision floats
* IP Addresses
* Ethernet Addresses
@@ -68,6 +69,8 @@ The format of the list file must be:
* ``<UINT16>port_id``
+ * ``<FLOAT_SINGLE>ratio``
+
* ``<IP>src_ip``
* ``<IPv4>dst_ip4``
diff --git a/doc/guides/rel_notes/release_25_07.rst b/doc/guides/rel_notes/release_25_07.rst
index 093b85d206..54bc545110 100644
--- a/doc/guides/rel_notes/release_25_07.rst
+++ b/doc/guides/rel_notes/release_25_07.rst
@@ -55,6 +55,11 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added floating point numbers support to cmdline library.**
+
+ The cmdline library now supports parsing single- and double-precision
+ floating point numbers in interactive user commands.
+
Removed Items
-------------
diff --git a/lib/cmdline/cmdline_parse_num.c b/lib/cmdline/cmdline_parse_num.c
index 88435d069e..5ea65a4d4a 100644
--- a/lib/cmdline/cmdline_parse_num.c
+++ b/lib/cmdline/cmdline_parse_num.c
@@ -8,6 +8,8 @@
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
+#include <float.h>
+#include <math.h>
#include <string.h>
#include <eal_export.h>
#include <rte_string_fns.h>
@@ -34,6 +36,7 @@ struct cmdline_token_ops cmdline_token_num_ops = {
static const char * num_help[] = {
"UINT8", "UINT16", "UINT32", "UINT64",
"INT8", "INT16", "INT32", "INT64",
+ "FLOAT_SINGLE", "FLOAT_DOUBLE"
};
static inline int
@@ -71,6 +74,14 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
if (ressize < sizeof(int64_t))
return -1;
break;
+ case RTE_FLOAT_SINGLE:
+ if (ressize < sizeof(float))
+ return -1;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ if (ressize < sizeof(double))
+ return -1;
+ break;
default:
debug_printf("Wrong number type: %d\n", nd->type);
return -1;
@@ -81,7 +92,7 @@ check_res_size(struct cmdline_token_num_data *nd, unsigned ressize)
static int
validate_type(enum cmdline_numtype type)
{
- if (type < RTE_UINT8 || type > RTE_INT64)
+ if (type < RTE_UINT8 || type > RTE_FLOAT_DOUBLE)
return -1;
/* ensure no buffer overrun can occur */
if ((uint64_t) type >= RTE_DIM(num_help))
@@ -162,6 +173,26 @@ parse_num(const char *srcbuf, uint64_t *resptr)
return end - srcbuf;
}
+static int
+parse_float(const char *srcbuf, enum cmdline_numtype type, double *resptr)
+{
+ double dres;
+ char *end;
+
+ if (type == RTE_FLOAT_SINGLE)
+ dres = (double)strtof(srcbuf, &end);
+ else if (type == RTE_FLOAT_DOUBLE)
+ dres = strtod(srcbuf, &end);
+ else
+ return -1;
+
+ if (end == srcbuf || !cmdline_isendoftoken(*end) || isinf(dres))
+ return -1;
+
+ *resptr = dres;
+ return end - srcbuf;
+}
+
static int
parse_bin(const char *srcbuf, uint64_t *res)
{
@@ -286,7 +317,24 @@ write_num(enum cmdline_numtype type, void *res, uint64_t uintres)
return 0;
}
-/* parse an int */
+static int
+write_float(enum cmdline_numtype type, void *res, double dres)
+{
+ switch (type) {
+ case RTE_FLOAT_SINGLE:
+ *(float *)res = (float)dres;
+ break;
+ case RTE_FLOAT_DOUBLE:
+ *(double *)res = dres;
+ break;
+ default:
+ debug_printf("Wrong float type\n");
+ return -1;
+ }
+ return 0;
+}
+
+/* parse a number */
RTE_EXPORT_SYMBOL(cmdline_parse_num)
int
cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
@@ -309,6 +357,7 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
if (res && check_res_size(&nd, ressize) < 0)
return -1;
+ /* integer parsing */
if (nd.type >= RTE_UINT8 && nd.type <= RTE_INT64) {
int ret, neg = *srcbuf == '-';
uint64_t uintres;
@@ -330,6 +379,21 @@ cmdline_parse_num(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res,
if (res && write_num(nd.type, res, uintres) < 0)
return -1;
+ return ret;
+ /* float parsing */
+ } else if (nd.type >= RTE_FLOAT_SINGLE && nd.type <= RTE_FLOAT_DOUBLE) {
+ double dres;
+ int ret;
+
+ /* try parsing as float */
+ ret = parse_float(srcbuf, nd.type, &dres);
+ if (ret < 0)
+ return -1;
+
+ /* parsing succeeded, write the value if necessary */
+ if (res && write_float(nd.type, res, dres))
+ return -1;
+
return ret;
}
return -1;
diff --git a/lib/cmdline/cmdline_parse_num.h b/lib/cmdline/cmdline_parse_num.h
index bdd0267612..b2792a2d11 100644
--- a/lib/cmdline/cmdline_parse_num.h
+++ b/lib/cmdline/cmdline_parse_num.h
@@ -22,7 +22,9 @@ enum cmdline_numtype {
RTE_INT8,
RTE_INT16,
RTE_INT32,
- RTE_INT64
+ RTE_INT64,
+ RTE_FLOAT_SINGLE,
+ RTE_FLOAT_DOUBLE,
};
struct cmdline_token_num_data {
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v10 3/3] app/testpmd: add sleep command
2025-05-09 14:41 ` [PATCH v10 " Anatoly Burakov
2025-05-09 14:41 ` [PATCH v10 2/3] cmdline: add floating point support Anatoly Burakov
@ 2025-05-09 14:41 ` Anatoly Burakov
1 sibling, 0 replies; 45+ messages in thread
From: Anatoly Burakov @ 2025-05-09 14:41 UTC (permalink / raw)
To: dev, Aman Singh
Test-pmd already has a way to run a list of commands from file, but there
is no way to pause execution for a specified amount of time between two
commands. This may be necessary for simple automation, particularly for
waiting on some asynchronous operation such as link status update.
Add a simple sleep command to wait until certain number of seconds has
passed, using the newly added cmdline library floating point support.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
Notes:
v1 -> v2:
- Add floating point support to cmdline
- Use floating point format for pause command
app/test-pmd/cmdline.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index d4bb3ec998..b6152c07e6 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -151,6 +151,9 @@ static void cmd_help_long_parsed(void *parsed_result,
"quit\n"
" Quit to prompt.\n\n"
+
+ "sleep secs\n"
+ " Sleep for secs seconds (can be fractional).\n\n"
);
}
@@ -7768,6 +7771,37 @@ static cmdline_parse_inst_t cmd_quit = {
},
};
+/* *** SLEEP *** */
+struct cmd_sleep_result {
+ cmdline_fixed_string_t sleep;
+ double secs;
+};
+
+static void cmd_sleep_parsed(void *parsed_result,
+ __rte_unused struct cmdline *cl,
+ __rte_unused void *data)
+{
+ struct cmd_sleep_result *res = parsed_result;
+
+ rte_delay_us_sleep(res->secs * 1E6);
+}
+
+static cmdline_parse_token_string_t cmd_sleep_sleep =
+ TOKEN_STRING_INITIALIZER(struct cmd_sleep_result, sleep, "sleep");
+static cmdline_parse_token_num_t cmd_sleep_seconds =
+ TOKEN_NUM_INITIALIZER(struct cmd_sleep_result, secs, RTE_FLOAT_DOUBLE);
+
+static cmdline_parse_inst_t cmd_sleep = {
+ .f = cmd_sleep_parsed,
+ .data = NULL,
+ .help_str = "sleep <secs>: Sleep for a specified number of seconds",
+ .tokens = {
+ (void *)&cmd_sleep_sleep,
+ (void *)&cmd_sleep_seconds,
+ NULL,
+ },
+};
+
/* *** ADD/REMOVE MAC ADDRESS FROM A PORT *** */
struct cmd_mac_addr_result {
cmdline_fixed_string_t mac_addr_cmd;
@@ -13711,6 +13745,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
&cmd_showdevice,
&cmd_showcfg,
&cmd_showfwdall,
+ &cmd_sleep,
&cmd_start,
&cmd_start_tx_first,
&cmd_start_tx_first_n,
--
2.47.1
^ permalink raw reply [flat|nested] 45+ messages in thread
end of thread, other threads:[~2025-05-09 14:42 UTC | newest]
Thread overview: 45+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-05-02 12:27 [PATCH v1 1/1] app/testpmd: add sleep command Anatoly Burakov
2025-05-02 12:37 ` Bruce Richardson
2025-05-02 14:35 ` Burakov, Anatoly
2025-05-02 14:43 ` Bruce Richardson
2025-05-02 15:33 ` Morten Brørup
2025-05-02 15:42 ` Stephen Hemminger
2025-05-06 12:36 ` Burakov, Anatoly
2025-05-06 13:08 ` [PATCH v2 1/2] cmdline: add floating point support Anatoly Burakov
2025-05-06 13:08 ` [PATCH v2 2/2] app/testpmd: add sleep command Anatoly Burakov
2025-05-06 13:38 ` [PATCH v2 1/2] cmdline: add floating point support Bruce Richardson
2025-05-07 9:02 ` Burakov, Anatoly
2025-05-07 9:50 ` [PATCH v3 " Anatoly Burakov
2025-05-07 9:50 ` [PATCH v3 2/2] app/testpmd: add sleep command Anatoly Burakov
2025-05-07 9:53 ` [PATCH v3 1/2] cmdline: add floating point support Burakov, Anatoly
2025-05-07 10:01 ` [PATCH v4 " Anatoly Burakov
2025-05-07 10:01 ` [PATCH v4 2/2] app/testpmd: add sleep command Anatoly Burakov
2025-05-07 10:35 ` [PATCH v4 1/2] cmdline: add floating point support Konstantin Ananyev
2025-05-07 11:06 ` Burakov, Anatoly
2025-05-07 12:24 ` Konstantin Ananyev
2025-05-07 14:06 ` Burakov, Anatoly
2025-05-07 15:22 ` [PATCH v5 1/3] cmdline: use C standard library as number parser Anatoly Burakov
2025-05-07 15:22 ` [PATCH v5 2/3] cmdline: add floating point support Anatoly Burakov
2025-05-07 15:22 ` [PATCH v5 3/3] app/testpmd: add sleep command Anatoly Burakov
2025-05-08 7:27 ` [PATCH v5 1/3] cmdline: use C standard library as number parser Bruce Richardson
2025-05-08 8:35 ` Burakov, Anatoly
2025-05-08 9:53 ` [PATCH v6 " Anatoly Burakov
2025-05-08 9:53 ` [PATCH v6 2/3] cmdline: add floating point support Anatoly Burakov
2025-05-08 9:53 ` [PATCH v6 3/3] app/testpmd: add sleep command Anatoly Burakov
2025-05-08 10:01 ` [PATCH v7 1/3] cmdline: use C standard library as number parser Anatoly Burakov
2025-05-08 10:01 ` [PATCH v7 2/3] cmdline: add floating point support Anatoly Burakov
2025-05-08 10:09 ` Burakov, Anatoly
2025-05-08 10:01 ` [PATCH v7 3/3] app/testpmd: add sleep command Anatoly Burakov
2025-05-08 13:16 ` [PATCH v8 1/3] cmdline: use C standard library as number parser Anatoly Burakov
2025-05-08 13:16 ` [PATCH v8 2/3] cmdline: add floating point support Anatoly Burakov
2025-05-08 13:16 ` [PATCH v8 3/3] app/testpmd: add sleep command Anatoly Burakov
2025-05-09 13:02 ` [PATCH v8 1/3] cmdline: use C standard library as number parser Burakov, Anatoly
2025-05-09 13:08 ` Burakov, Anatoly
2025-05-09 13:27 ` Burakov, Anatoly
2025-05-09 13:39 ` [PATCH v9 " Anatoly Burakov
2025-05-09 13:39 ` [PATCH v9 2/3] cmdline: add floating point support Anatoly Burakov
2025-05-09 13:39 ` [PATCH v9 3/3] app/testpmd: add sleep command Anatoly Burakov
2025-05-09 13:42 ` [PATCH v9 1/3] cmdline: use C standard library as number parser Burakov, Anatoly
2025-05-09 14:41 ` [PATCH v10 " Anatoly Burakov
2025-05-09 14:41 ` [PATCH v10 2/3] cmdline: add floating point support Anatoly Burakov
2025-05-09 14:41 ` [PATCH v10 3/3] app/testpmd: add sleep command Anatoly Burakov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).