[v8,02/24] ethdev: add a link status text representation
diff mbox series

Message ID 20200711104414.15422-3-i.dyukov@samsung.com
State Deferred
Delegated to: Ferruh Yigit
Headers show
Series
  • ethdev: allow unknown link speed
Related show

Checks

Context Check Description
ci/Intel-compilation success Compilation OK
ci/checkpatch success coding style OK

Commit Message

Ivan Dyukov July 11, 2020, 10:43 a.m. UTC
link status structure keeps complicated values which are hard to
represent to end user. e.g. link_speed has INT_MAX value which
means that speed is unknown. To simplify processing of the values
in application, new dpdk function is introduced.

This commit adds function which treat link status structure
and format it to text representation.

Signed-off-by: Ivan Dyukov <i.dyukov@samsung.com>
---
 MAINTAINERS                              |   1 +
 app/test/Makefile                        |   3 +
 app/test/meson.build                     |   2 +
 app/test/test_ethdev_link.c              | 306 +++++++++++++++++++++++
 lib/librte_ethdev/rte_ethdev.c           | 174 +++++++++++++
 lib/librte_ethdev/rte_ethdev.h           |  56 +++++
 lib/librte_ethdev/rte_ethdev_version.map |   4 +
 7 files changed, 546 insertions(+)
 create mode 100644 app/test/test_ethdev_link.c

Comments

Thomas Monjalon July 11, 2020, 11:27 a.m. UTC | #1
11/07/2020 12:43, Ivan Dyukov:
> +__rte_experimental
> +int rte_eth_link_printf(const char *const fmt,
> +                       const struct rte_eth_link *eth_link);
> 

Maybe I missed your reply,
I still don't understand the need for this function.
Ivan Dyukov July 11, 2020, 6:58 p.m. UTC | #2
11.07.2020 14:27, Thomas Monjalon пишет:
> 11/07/2020 12:43, Ivan Dyukov:
>> +__rte_experimental
>> +int rte_eth_link_printf(const char *const fmt,
>> +                       const struct rte_eth_link *eth_link);
>>
> Maybe I missed your reply,
> I still don't understand the need for this function.
>
>
>
>
I used it few times in apps in this patchset.  It allows to avoid code 
duplication in examples. i.e. declare array, call rte_eth_link_to_str, 
call printf.

It's simple and usefull function.  Why not?
Thomas Monjalon July 12, 2020, 7:35 a.m. UTC | #3
11/07/2020 20:58, Ivan Dyukov:
> 11.07.2020 14:27, Thomas Monjalon пишет:
> > 11/07/2020 12:43, Ivan Dyukov:
> >> +__rte_experimental
> >> +int rte_eth_link_printf(const char *const fmt,
> >> +                       const struct rte_eth_link *eth_link);
> >>
> > Maybe I missed your reply,
> > I still don't understand the need for this function.
> >
> I used it few times in apps in this patchset.  It allows to avoid code 
> duplication in examples. i.e. declare array, call rte_eth_link_to_str, 
> call printf.
> 
> It's simple and usefull function.  Why not?

Why not is not a good justification :)
We must avoid adding too much in the API.
Stephen Hemminger July 12, 2020, 7:21 p.m. UTC | #4
On Sun, 12 Jul 2020 09:35:23 +0200
Thomas Monjalon <thomas@monjalon.net> wrote:

> 11/07/2020 20:58, Ivan Dyukov:
> > 11.07.2020 14:27, Thomas Monjalon пишет:  
> > > 11/07/2020 12:43, Ivan Dyukov:  
> > >> +__rte_experimental
> > >> +int rte_eth_link_printf(const char *const fmt,
> > >> +                       const struct rte_eth_link *eth_link);
> > >>  
> > > Maybe I missed your reply,
> > > I still don't understand the need for this function.
> > >  
> > I used it few times in apps in this patchset.  It allows to avoid code 
> > duplication in examples. i.e. declare array, call rte_eth_link_to_str, 
> > call printf.
> > 
> > It's simple and usefull function.  Why not?  
> 
> Why not is not a good justification :)
> We must avoid adding too much in the API.
> 
> 

I agree with Thomas, this whole exercise seems like a lot of effort for
a log message that probably should be debug only.

The DPDK should adopt the policy that all core and drivers print
no log messages by default, and only log at NOTICE or  higher in case
of error.
Ivan Dyukov July 12, 2020, 8:09 p.m. UTC | #5
12.07.2020 10:35, Thomas Monjalon пишет:
> 11/07/2020 20:58, Ivan Dyukov:
>> 11.07.2020 14:27, Thomas Monjalon пишет:
>>> 11/07/2020 12:43, Ivan Dyukov:
>>>> +__rte_experimental
>>>> +int rte_eth_link_printf(const char *const fmt,
>>>> +                       const struct rte_eth_link *eth_link);
>>>>
>>> Maybe I missed your reply,
>>> I still don't understand the need for this function.
>>>
>> I used it few times in apps in this patchset.  It allows to avoid code
>> duplication in examples. i.e. declare array, call rte_eth_link_to_str,
>> call printf.
>>
>> It's simple and usefull function.  Why not?
> Why not is not a good justification :)
I still don't understand your objections against this tiny shiny function.
> We must avoid adding too much in the API.
>
>
I agree that this function is superfluous for DPDK library, but I would 
note that it's not just 'DPDK library' change, it's 'DPDK library + DPDK 
examples' change. With this change, the entire code is getting better. 
The new function adds usefull functionality. It allows to reduce code 
size and remove duplicates. so technically it's good change but 
practically I would like to ask maintainers to decide this.


P.S.

I'll we on vacation next two weeks. May be I'll check email but I don't 
guarantee that.
Morten Brørup July 13, 2020, 7:58 a.m. UTC | #6
> From: dev [mailto:dev-bounces@dpdk.org] On Behalf Of Stephen Hemminger
> Sent: Sunday, July 12, 2020 9:21 PM
> 
> On Sun, 12 Jul 2020 09:35:23 +0200
> Thomas Monjalon <thomas@monjalon.net> wrote:
> 
> > 11/07/2020 20:58, Ivan Dyukov:
> > > 11.07.2020 14:27, Thomas Monjalon пишет:
> > > > 11/07/2020 12:43, Ivan Dyukov:
> > > >> +__rte_experimental
> > > >> +int rte_eth_link_printf(const char *const fmt,
> > > >> +                       const struct rte_eth_link *eth_link);
> > > >>
> > > > Maybe I missed your reply,
> > > > I still don't understand the need for this function.
> > > >
> > > I used it few times in apps in this patchset.  It allows to avoid code
> > > duplication in examples. i.e. declare array, call rte_eth_link_to_str,
> > > call printf.
> > >
> > > It's simple and usefull function.  Why not?
> >
> > Why not is not a good justification :)
> > We must avoid adding too much in the API.
> >
> >
> 
> I agree with Thomas, this whole exercise seems like a lot of effort for
> a log message that probably should be debug only.

Adding a text formatting function to DPDK is questionable, but that was already discussed and approved. And I agree that it is generic enough to be useful.

However, providing a wrapper function to printf the output from a text formatting function is not useful. The application can do that. Who is to say that stdout is the right output channel for the application anyway... it might as well be a debug log, SYSLOG or something else. Certainly application specific.

> 
> The DPDK should adopt the policy that all core and drivers print
> no log messages by default, and only log at NOTICE or  higher in case
> of error.

+1

I am totally with Stephen here! Any printf will kill performance. If anything, use the log or trace libraries. And the log library does not exactly provide "data plane performance", so it should be used with extreme caution.

On a side note: It looks like Cisco considers link status change events ERROR severity for operational state changes and NOTICE severity for administrative state changes. Source: https://networklessons.com/cisco/ccie-routing-switching/cisco-ios-syslog-messages

Patch
diff mbox series

diff --git a/MAINTAINERS b/MAINTAINERS
index 5e706cd7e..f4fb31ea2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -393,6 +393,7 @@  T: git://dpdk.org/next/dpdk-next-net
 F: lib/librte_ethdev/
 F: devtools/test-null.sh
 F: doc/guides/prog_guide/switch_representation.rst
+F: app/test/test_ethdev*
 
 Flow API
 M: Ori Kam <orika@mellanox.com>
diff --git a/app/test/Makefile b/app/test/Makefile
index e5440774b..9f43b8c3c 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -251,6 +251,9 @@  SRCS-$(CONFIG_RTE_LIBRTE_SECURITY) += test_security.c
 
 SRCS-$(CONFIG_RTE_LIBRTE_IPSEC) += test_ipsec.c test_ipsec_perf.c
 SRCS-$(CONFIG_RTE_LIBRTE_IPSEC) += test_ipsec_sad.c
+
+SRCS-$(CONFIG_RTE_LIBRTE_ETHER) += test_ethdev_link.c
+
 ifeq ($(CONFIG_RTE_LIBRTE_IPSEC),y)
 LDLIBS += -lrte_ipsec
 endif
diff --git a/app/test/meson.build b/app/test/meson.build
index 56591db4e..1e6acf701 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -39,6 +39,7 @@  test_sources = files('commands.c',
 	'test_efd.c',
 	'test_efd_perf.c',
 	'test_errno.c',
+	'test_ethdev_link.c',
 	'test_event_crypto_adapter.c',
 	'test_event_eth_rx_adapter.c',
 	'test_event_ring.c',
@@ -199,6 +200,7 @@  fast_tests = [
         ['eal_flags_misc_autotest', false],
         ['eal_fs_autotest', true],
         ['errno_autotest', true],
+        ['ethdev_link_status', true],
         ['event_ring_autotest', true],
         ['fib_autotest', true],
         ['fib6_autotest', true],
diff --git a/app/test/test_ethdev_link.c b/app/test/test_ethdev_link.c
new file mode 100644
index 000000000..01e57a0f3
--- /dev/null
+++ b/app/test/test_ethdev_link.c
@@ -0,0 +1,306 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved
+ */
+
+#include <rte_log.h>
+#include <rte_ethdev.h>
+
+#include <rte_test.h>
+#include "test.h"
+
+
+static int32_t
+test_link_status_up_default(void)
+{
+	int ret = 0;
+	struct rte_eth_link link_status = {
+		.link_speed = ETH_SPEED_NUM_2_5G,
+		.link_status = ETH_LINK_UP,
+		.link_autoneg = ETH_LINK_AUTONEG,
+		.link_duplex = ETH_LINK_FULL_DUPLEX
+	};
+	char text[128];
+
+	ret = rte_eth_link_to_str(text, 128, NULL, &link_status);
+	RTE_TEST_ASSERT(ret > 0, "Failed to format default string\n");
+	printf("Default link up #1: %s\n", text);
+	TEST_ASSERT_BUFFERS_ARE_EQUAL("Link up at 2.5 Gbit/s FDX Autoneg\n",
+		text, strlen(text), "Invalid default link status string");
+
+	link_status.link_duplex = ETH_LINK_HALF_DUPLEX;
+	link_status.link_autoneg = ETH_LINK_FIXED;
+	link_status.link_speed = ETH_SPEED_NUM_10M,
+	ret = rte_eth_link_to_str(text, 128, NULL, &link_status);
+	printf("Default link up #2: %s\n", text);
+	RTE_TEST_ASSERT(ret > 0, "Failed to format default string\n");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL("Link up at 10 Mbit/s HDX Fixed\n",
+		text, strlen(text), "Invalid default link status "
+		"string with HDX");
+
+	link_status.link_speed = ETH_SPEED_NUM_UNKNOWN,
+	ret = rte_eth_link_to_str(text, 128, NULL, &link_status);
+	printf("Default link up #3: %s\n", text);
+	RTE_TEST_ASSERT(ret > 0, "Failed to format default string\n");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL("Link up at Unknown speed HDX Fixed\n",
+		text, strlen(text), "Invalid default link status "
+		"string with HDX");
+	return TEST_SUCCESS;
+}
+
+static int32_t
+test_link_status_down_default(void)
+{
+	int ret = 0;
+	struct rte_eth_link link_status = {
+		.link_speed = ETH_SPEED_NUM_2_5G,
+		.link_status = ETH_LINK_DOWN,
+		.link_autoneg = ETH_LINK_AUTONEG,
+		.link_duplex = ETH_LINK_FULL_DUPLEX
+	};
+	char text[128];
+
+	ret = rte_eth_link_to_str(text, 128, NULL, &link_status);
+	RTE_TEST_ASSERT(ret > 0, "Failed to format default string\n");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL("Link down\n",
+		text, strlen(text), "Invalid default link status string");
+
+	return TEST_SUCCESS;
+}
+
+static int32_t
+test_link_status_string_overflow(void)
+{
+	int ret = 0;
+	struct rte_eth_link link_status = {
+		.link_speed = ETH_SPEED_NUM_2_5G,
+		.link_status = ETH_LINK_UP,
+		.link_autoneg = ETH_LINK_AUTONEG,
+		.link_duplex = ETH_LINK_FULL_DUPLEX
+	};
+	char text[128];
+	int i = 0;
+
+	for (i = 0; i < 128; i++)
+		text[i] = 'Y';
+	text[127] = '\0';
+
+	ret = rte_eth_link_to_str(NULL, 2, "status %S, %G Gbits/s",
+		&link_status);
+	RTE_TEST_ASSERT(ret < 0, "Format string should fail, but it's ok\n");
+
+	ret = rte_eth_link_to_str(text, 2, "status %S, %G Gbits/s",
+		&link_status);
+	RTE_TEST_ASSERT(ret < 0, "Format string should fail, but it's ok\n");
+	RTE_TEST_ASSERT(text[2] == 'Y', "String1 overflow\n");
+
+	ret = rte_eth_link_to_str(text, 8, NULL,
+		&link_status);
+	RTE_TEST_ASSERT(ret < 0, "Default format string should fail,"
+			" but it's ok\n");
+	RTE_TEST_ASSERT(text[8] == 'Y', "String1 overflow\n");
+
+	ret = rte_eth_link_to_str(text, 10, NULL,
+		&link_status);
+	RTE_TEST_ASSERT(ret < 0, "Default format string should fail,"
+			" but it's ok\n");
+	RTE_TEST_ASSERT(text[10] == 'Y', "String1 overflow\n");
+
+	text[1] = 'Y';
+	ret = rte_eth_link_to_str(text, 1, "%S",
+		&link_status);
+	RTE_TEST_ASSERT(ret < 0, "Status string should fail, but it's ok\n");
+	RTE_TEST_ASSERT(text[1] == 'Y', "String1 overflow\n");
+
+	text[1] = 'Y';
+	ret = rte_eth_link_to_str(text, 1, "%s",
+		&link_status);
+	RTE_TEST_ASSERT(ret < 0, "Status string should fail, but it's ok\n");
+	RTE_TEST_ASSERT(text[1] == 'Y', "String1 overflow\n");
+
+
+	return TEST_SUCCESS;
+}
+
+static int32_t
+test_link_status_format(void)
+{
+	int ret = 0;
+	struct rte_eth_link link_status = {
+		.link_speed = ETH_SPEED_NUM_40G,
+		.link_status = ETH_LINK_UP,
+		.link_autoneg = ETH_LINK_AUTONEG,
+		.link_duplex = ETH_LINK_FULL_DUPLEX
+	};
+	char text[128];
+	int i = 0;
+
+	for (i = 0; i < 128; i++)
+		text[i] = 'Y';
+	text[127] = '\0';
+	printf("status format #1: %s\n", text);
+	ret = rte_eth_link_to_str(text, 128, "status = %S, duplex = %D\n",
+		&link_status);
+	printf("status format #2: %s\n", text);
+	RTE_TEST_ASSERT(ret > 0, "Failed to format string\n");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL("status = Up, duplex = FDX\n",
+		text, strlen(text), "Invalid status string1.");
+
+	ret = rte_eth_link_to_str(text, 128, "%A", &link_status);
+	printf("status format #3: %s\n", text);
+	RTE_TEST_ASSERT(ret > 0, "Failed to format string\n");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL("Autoneg",
+		text, strlen(text), "Invalid status string2.");
+
+	ret = rte_eth_link_to_str(text, 128,
+		"%G",
+		&link_status);
+	printf("status format #4: %s\n", text);
+	RTE_TEST_ASSERT(ret > 0, "Failed to format string\n");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL("40.0",
+		text, strlen(text), "Invalid status string3.");
+
+	ret = rte_eth_link_to_str(text, 128,
+		"%d %M %",
+		&link_status);
+	printf("status format #5: %s\n", text);
+	RTE_TEST_ASSERT(ret > 0, "Failed to format string\n");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL("%d 40000 %",
+		text, strlen(text), "Invalid status string4.");
+	return TEST_SUCCESS;
+}
+
+static int32_t
+test_link_status_return_value(void)
+{
+	int ret = 0;
+	struct rte_eth_link link_status = {
+		.link_speed = ETH_SPEED_NUM_40G,
+		.link_status = ETH_LINK_UP,
+		.link_autoneg = ETH_LINK_AUTONEG,
+		.link_duplex = ETH_LINK_FULL_DUPLEX
+	};
+	char text[128];
+	int i = 0;
+
+	for (i = 0; i < 128; i++)
+		text[i] = 'Y';
+	text[127] = '\0';
+	ret = rte_eth_link_to_str(text, 128, "status = %S, ",
+		&link_status);
+	printf("return value #1:ret=%u, text=%s\n", ret, text);
+	ret += rte_eth_link_to_str(text + ret, 128 - ret,
+		"%A",
+		&link_status);
+	printf("return value #2:ret=%u, text=%s\n", ret, text);
+	ret += rte_eth_link_to_str(text + ret, 128 - ret,
+		", duplex = %D\n",
+		&link_status);
+	printf("return value #3:ret=%u, text=%s\n", ret, text);
+	ret += rte_eth_link_to_str(text + ret, 128 - ret,
+		"%M Mbits/s\n",
+		&link_status);
+	printf("return value #4:ret=%u, text=%s\n", ret, text);
+	RTE_TEST_ASSERT(ret > 0, "Failed to format string\n");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL("status = Up, Autoneg, duplex = FDX\n"
+		"40000 Mbits/s\n",
+		text, strlen(text), "Invalid status string");
+
+	return TEST_SUCCESS;
+}
+
+static int32_t
+test_link_status_unknown_specifier(void)
+{
+	int ret = 0;
+	struct rte_eth_link link_status = {
+		.link_speed = ETH_SPEED_NUM_40G,
+		.link_status = ETH_LINK_UP,
+		.link_autoneg = ETH_LINK_AUTONEG,
+		.link_duplex = ETH_LINK_FULL_DUPLEX
+	};
+	char text[128];
+
+	ret = rte_eth_link_to_str(text, 128, "status = %",
+		&link_status);
+	RTE_TEST_ASSERT(ret > 0, "Status string1 is failed\n");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL("status = %",
+		text, strlen(text), "Invalid status string1");
+
+	ret = rte_eth_link_to_str(text, 128,
+		", duplex = %d\n",
+		&link_status);
+	RTE_TEST_ASSERT(ret > 0, "Status string2 is failed\n");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(", duplex = %d\n",
+		text, strlen(text), "Invalid status string2");
+
+	ret = rte_eth_link_to_str(text, 128,
+		"% Mbits/s\n",
+		&link_status);
+	RTE_TEST_ASSERT(ret > 0, "Status string3 is failed\n");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL("% Mbits/s\n",
+		text, strlen(text), "Invalid status string3");
+
+	ret = rte_eth_link_to_str(text, 128,
+		"%w Mbits/s\n",
+		&link_status);
+	RTE_TEST_ASSERT(ret > 0, "Status string4 should be ok, but it's fail\n");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL("%w Mbits/s\n",
+		text, strlen(text), "Invalid status string4");
+	return TEST_SUCCESS;
+}
+
+static int32_t
+test_link_status_format_edges(void)
+{
+	int ret = 0;
+	struct rte_eth_link link_status = {
+		.link_speed = ETH_SPEED_NUM_UNKNOWN,
+		.link_status = ETH_LINK_DOWN,
+		.link_autoneg = ETH_LINK_AUTONEG,
+		.link_duplex = ETH_LINK_HALF_DUPLEX
+	};
+	char text[128];
+
+	ret = rte_eth_link_to_str(text, 4, "%S", &link_status);
+	printf("format edges #1: %s\n", text);
+	RTE_TEST_ASSERT(ret < 0, "It should fail. No space for "
+				 "zero terminator\n");
+	ret = rte_eth_link_to_str(text, 6, "123%D", &link_status);
+	printf("format edges #2: %s\n", text);
+	RTE_TEST_ASSERT(ret < 0, "It should fail. No space for "
+				 "zero terminator\n");
+	ret = rte_eth_link_to_str(text, 7, "%A", &link_status);
+	printf("format edges #3: %s\n", text);
+	RTE_TEST_ASSERT(ret < 0, "It should fail. No space for "
+				 "zero terminator\n");
+	ret = rte_eth_link_to_str(text, 8, "%A", &link_status);
+	printf("format edges #4: %s\n", text);
+	RTE_TEST_ASSERT(ret > 0, "It should ok, but it fails\n");
+	return TEST_SUCCESS;
+}
+static struct unit_test_suite link_status_testsuite = {
+	.suite_name = "link status formatting",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+		TEST_CASE(test_link_status_up_default),
+		TEST_CASE(test_link_status_down_default),
+		TEST_CASE(test_link_status_string_overflow),
+		TEST_CASE(test_link_status_format),
+		TEST_CASE(test_link_status_format_edges),
+		TEST_CASE(test_link_status_unknown_specifier),
+		TEST_CASE(test_link_status_return_value),
+		TEST_CASES_END() /**< NULL terminate unit test array */
+	}
+};
+
+static int
+test_link_status(void)
+{
+	rte_log_set_global_level(RTE_LOG_DEBUG);
+	rte_log_set_level(RTE_LOGTYPE_EAL, RTE_LOG_DEBUG);
+
+	return unit_test_suite_runner(&link_status_testsuite);
+}
+
+REGISTER_TEST_COMMAND(ethdev_link_status, test_link_status);
diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
index d06b7f9b1..d844a5b7b 100644
--- a/lib/librte_ethdev/rte_ethdev.c
+++ b/lib/librte_ethdev/rte_ethdev.c
@@ -2383,6 +2383,180 @@  rte_eth_link_get_nowait(uint16_t port_id, struct rte_eth_link *eth_link)
 	return 0;
 }
 
+static int
+rte_eth_link_to_str_parser(char *str, size_t len, const char *const fmt,
+			   const struct rte_eth_link *eth_link)
+{
+	size_t offset = 0;
+	const char *fmt_cur = fmt;
+	double gbits = (double)eth_link->link_speed / 1000.;
+	static const char autoneg_str[]       = "Autoneg";
+	static const char fixed_str[]         = "Fixed";
+	static const char fdx_str[]           = "FDX";
+	static const char hdx_str[]           = "HDX";
+	static const char unknown_str[]       = "Unknown";
+	static const char up_str[]            = "Up";
+	static const char down_str[]          = "Down";
+	char gbits_str[20];
+	char mbits_str[20];
+
+	/* preformat complex formatting to easily concatinate it further */
+	snprintf(mbits_str, sizeof(mbits_str), "%u", eth_link->link_speed);
+	snprintf(gbits_str, sizeof(gbits_str), "%.1f", gbits);
+	/* init str before formatting */
+	str[0] = 0;
+	while (*fmt_cur) {
+		/* check str bounds */
+		if (offset >= len) {
+			str[len - 1] = '\0';
+			return -EINVAL;
+		}
+		if (*fmt_cur == '%') {
+			/* set null terminator to current position,
+			 * it's required for strlcat
+			 */
+			str[offset] = '\0';
+			switch (*++fmt_cur) {
+			/* Speed in Mbits/s */
+			case 'M':
+				if (eth_link->link_speed ==
+				    ETH_SPEED_NUM_UNKNOWN)
+					offset = strlcat(str, unknown_str,
+							 len);
+				else
+					offset = strlcat(str, mbits_str, len);
+				break;
+			/* Speed in Gbits/s */
+			case 'G':
+				if (eth_link->link_speed ==
+				    ETH_SPEED_NUM_UNKNOWN)
+					offset = strlcat(str, unknown_str,
+							 len);
+				else
+					offset = strlcat(str, gbits_str, len);
+				break;
+			/* Link status */
+			case 'S':
+				offset = strlcat(str, eth_link->link_status ?
+					up_str : down_str, len);
+				break;
+			/* Link autoneg */
+			case 'A':
+				offset = strlcat(str, eth_link->link_autoneg ?
+					autoneg_str : fixed_str, len);
+				break;
+			/* Link duplex */
+			case 'D':
+				offset = strlcat(str, eth_link->link_duplex ?
+					fdx_str : hdx_str, len);
+				break;
+			/* ignore unknown specifier
+			 * just copy it to target str
+			 */
+			default:
+				str[offset++] = '%';
+				fmt_cur--;
+				break;
+
+			}
+			if (offset >= len)
+				return -EINVAL;
+
+		} else {
+			str[offset++] = *fmt_cur;
+		}
+		fmt_cur++;
+	}
+	str[offset] = '\0';
+	return offset;
+}
+
+int
+rte_eth_link_printf(const char *const fmt,
+		    const struct rte_eth_link *eth_link)
+{
+	char text[200];
+	int ret;
+
+	ret = rte_eth_link_to_str(text, 200, fmt, eth_link);
+	if (ret > 0)
+		printf("%s", text);
+	return ret;
+}
+
+int
+rte_eth_link_to_str(char *str, size_t len, const char *const fmt,
+		    const struct rte_eth_link *eth_link)
+{
+	size_t offset = 0;
+	double gbits = (double)eth_link->link_speed / 1000.;
+	char speed_gbits_str[20];
+	char speed_mbits_str[20];
+	/* TBD: make it international? */
+	static const char link_down_str[]     = "Link down\n";
+	static const char link_up_str[]       = "Link up at ";
+	static const char unknown_speed_str[] = "Unknown speed ";
+	static const char mbits_str[]	      = "Mbit/s";
+	static const char gbits_str[]	      = "Gbit/s";
+	static const char fdx_str[]           = "FDX ";
+	static const char hdx_str[]           = "HDX ";
+	/* autoneg is latest param in default string, so add '\n' */
+	static const char autoneg_str[]       = "Autoneg\n";
+	static const char fixed_str[]         = "Fixed\n";
+
+	if (str == NULL || len == 0)
+		return -EINVAL;
+	/* default format string, if no fmt is specified */
+	if (fmt == NULL) {
+		if (eth_link->link_status == ETH_LINK_DOWN) {
+			if (sizeof(link_down_str) > len)
+				return -EINVAL;
+			return strlcpy(str, link_down_str, len);
+		}
+
+		/* preformat complex strings to easily concatinate it further */
+		snprintf(speed_mbits_str, sizeof(speed_mbits_str), "%u %s ",
+			 eth_link->link_speed, mbits_str);
+		snprintf(speed_gbits_str, sizeof(speed_gbits_str), "%.1f %s ",
+			 gbits, gbits_str);
+
+		offset = strlcpy(str, link_up_str, len);
+		/* reserve one byte to null terminator */
+		if (offset >= len)
+			return -EINVAL;
+		/* link speed */
+		if (eth_link->link_speed == ETH_SPEED_NUM_UNKNOWN) {
+			offset = strlcat(str, unknown_speed_str, len);
+			if (offset >= len)
+				return -EINVAL;
+		} else {
+			if (eth_link->link_speed < ETH_SPEED_NUM_1G) {
+				offset = strlcat(str, speed_mbits_str, len);
+				if (offset >= len)
+					return -EINVAL;
+			} else {
+				offset = strlcat(str, speed_gbits_str, len);
+				if (offset >= len)
+					return -EINVAL;
+			}
+		}
+		/* link duplex */
+		offset = strlcat(str, eth_link->link_duplex ?
+			       fdx_str : hdx_str, len);
+		if (offset >= len)
+			return -EINVAL;
+		/* link autonegotiation */
+		offset = strlcat(str, eth_link->link_autoneg ?
+			       autoneg_str : fixed_str, len);
+		if (offset >= len)
+			return -EINVAL;
+	/* Formatted status */
+	} else
+		offset = rte_eth_link_to_str_parser(str, len, fmt, eth_link);
+
+	return offset;
+}
+
 int
 rte_eth_stats_get(uint16_t port_id, struct rte_eth_stats *stats)
 {
diff --git a/lib/librte_ethdev/rte_ethdev.h b/lib/librte_ethdev/rte_ethdev.h
index 2090af501..eeb6ac50c 100644
--- a/lib/librte_ethdev/rte_ethdev.h
+++ b/lib/librte_ethdev/rte_ethdev.h
@@ -2295,6 +2295,62 @@  int rte_eth_link_get(uint16_t port_id, struct rte_eth_link *link);
  */
 int rte_eth_link_get_nowait(uint16_t port_id, struct rte_eth_link *link);
 
+
+/**
+ * print formatted link status to stdout. This function treats all
+ * special values like ETH_SPEED_NUM_UNKNOWN, ETH_LINK_DOWN etc. and convert
+ * them to textual representation.
+ *
+ * @param fmt
+ *   Format string which allows to format link status.
+ *   If NULL is provided, default formatting will be applied.
+ *   Following specifiers are available:
+ *    - '%M' link speed in Mbits/s
+ *    - '%G' link speed in Gbits/s
+ *    - '%S' link status. e.g. Up or Down
+ *    - '%A' link autonegotiation state
+ *    - '%D' link duplex state
+ * @param eth_link
+ *   Link status provided by rte_eth_link_get function
+ * @return
+ *   - Number of bytes written to stdout.
+ *   - (-EINVAL) if fmt is too long
+ *
+ */
+__rte_experimental
+int rte_eth_link_printf(const char *const fmt,
+			const struct rte_eth_link *eth_link);
+
+/**
+ * Format link status to textual representation. This function treats all
+ * special values like ETH_SPEED_NUM_UNKNOWN, ETH_LINK_DOWN etc. and convert
+ * them to textual representation.
+ *
+ * @param str
+ *   A pointer to a string to be filled with textual representation of
+ *   device status.
+ * @param len
+ *   Length of available memory at 'str' string.
+ * @param fmt
+ *   Format string which allows to format link status.
+ *   If NULL is provided, default formatting will be applied.
+ *   Following specifiers are available:
+ *    - '%M' link speed in Mbits/s
+ *    - '%G' link speed in Gbits/s
+ *    - '%S' link status. e.g. Up or Down
+ *    - '%A' link autonegotiation state
+ *    - '%D' link duplex state
+ * @param eth_link
+ *   Link status provided by rte_eth_link_get function
+ * @return
+ *   - Number of bytes written to str array.
+ *   - (-EINVAL) if size of target buffer is too small for the string
+ *
+ */
+__rte_experimental
+int rte_eth_link_to_str(char *str, size_t len, const char *const fmt,
+			const struct rte_eth_link *eth_link);
+
 /**
  * Retrieve the general I/O statistics of an Ethernet device.
  *
diff --git a/lib/librte_ethdev/rte_ethdev_version.map b/lib/librte_ethdev/rte_ethdev_version.map
index 715505604..79eb99ccd 100644
--- a/lib/librte_ethdev/rte_ethdev_version.map
+++ b/lib/librte_ethdev/rte_ethdev_version.map
@@ -241,4 +241,8 @@  EXPERIMENTAL {
 	__rte_ethdev_trace_rx_burst;
 	__rte_ethdev_trace_tx_burst;
 	rte_flow_get_aged_flows;
+
+	# added in 20.08
+	rte_eth_link_to_str;
+	rte_eth_link_printf;
 };