DPDK patches and discussions
 help / color / mirror / Atom feed
* [dpdk-dev] [RFC 0/2] add basic ethdev stats with data object recursion
@ 2020-06-12 10:53 Ciara Power
  2020-06-12 10:53 ` [dpdk-dev] [RFC 1/2] telemetry: support some recursive data objects Ciara Power
                   ` (9 more replies)
  0 siblings, 10 replies; 44+ messages in thread
From: Ciara Power @ 2020-06-12 10:53 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: keith.wiles, dev, Ciara Power

This patchset adds support for basic ethdev statistics in Telemetry.
To do this, recursive data object support is needed to report the queue
statistics in a list. Currently, a dictionary can support a uint64_t
array value, which is used for the ethdev queue stats.

Ciara Power (2):
  telemetry: support some recursive data objects
  ethdev: add basic stats for telemetry

 lib/librte_ethdev/rte_ethdev.c                | 54 +++++++++++++++++++
 lib/librte_telemetry/rte_telemetry.h          | 27 ++++++++++
 .../rte_telemetry_version.map                 |  2 +
 lib/librte_telemetry/telemetry.c              | 34 ++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 18 +++++++
 lib/librte_telemetry/telemetry_data.h         |  3 ++
 lib/librte_telemetry/telemetry_json.h         | 17 ++++++
 7 files changed, 155 insertions(+)

-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [RFC 1/2] telemetry: support some recursive data objects
  2020-06-12 10:53 [dpdk-dev] [RFC 0/2] add basic ethdev stats with data object recursion Ciara Power
@ 2020-06-12 10:53 ` Ciara Power
  2020-06-12 12:58   ` Bruce Richardson
  2020-06-12 13:07   ` Bruce Richardson
  2020-06-12 10:53 ` [dpdk-dev] [RFC 2/2] ethdev: add basic stats for telemetry Ciara Power
                   ` (8 subsequent siblings)
  9 siblings, 2 replies; 44+ messages in thread
From: Ciara Power @ 2020-06-12 10:53 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: keith.wiles, dev, Ciara Power

Dict data objects now support uint64_t array data object values.
Only one level of recursion supported.

Signed-off-by: Ciara Power <ciara.power@intel.com>
---
 lib/librte_telemetry/rte_telemetry.h          | 27 +++++++++++++++
 .../rte_telemetry_version.map                 |  2 ++
 lib/librte_telemetry/telemetry.c              | 34 +++++++++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 18 ++++++++++
 lib/librte_telemetry/telemetry_data.h         |  3 ++
 lib/librte_telemetry/telemetry_json.h         | 17 ++++++++++
 6 files changed, 101 insertions(+)

diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
index 2c3c96cf7..dc18c34d0 100644
--- a/lib/librte_telemetry/rte_telemetry.h
+++ b/lib/librte_telemetry/rte_telemetry.h
@@ -44,6 +44,7 @@ enum rte_tel_value_type {
 	RTE_TEL_STRING_VAL, /** a string value */
 	RTE_TEL_INT_VAL,    /** a signed 32-bit int value */
 	RTE_TEL_U64_VAL,    /** an unsigned 64-bit int value */
+	RTE_TEL_DATA_VAL,   /** a rte_tel_data pointer value */
 };
 
 /**
@@ -188,6 +189,22 @@ int
 rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 		const char *name, uint64_t val);
 
+/**
+ * Add a data object pointer to a dictionary.
+ * The dict must have been started by rte_tel_data_start_dict().
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param x
+ *   The data pointer to be returned in the dictionary
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_dict_data(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val);
+
 /**
  * This telemetry callback is used when registering a telemetry command.
  * It handles getting and formatting information to be returned to telemetry
@@ -253,4 +270,14 @@ int
 rte_telemetry_init(const char *runtime_dir, rte_cpuset_t *cpuset,
 		const char **err_str);
 
+/**
+ * Get the size of the rte_tel_data struct.
+ *
+ * @return
+ *  size_t of the struct
+ */
+__rte_experimental
+size_t
+rte_tel_get_data_size(void);
+
 #endif
diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map
index 86433c21d..c5425eff6 100644
--- a/lib/librte_telemetry/rte_telemetry_version.map
+++ b/lib/librte_telemetry/rte_telemetry_version.map
@@ -4,6 +4,7 @@ EXPERIMENTAL {
 	rte_tel_data_add_array_int;
 	rte_tel_data_add_array_string;
 	rte_tel_data_add_array_u64;
+	rte_tel_data_add_dict_data;
 	rte_tel_data_add_dict_int;
 	rte_tel_data_add_dict_string;
 	rte_tel_data_add_dict_u64;
@@ -13,6 +14,7 @@ EXPERIMENTAL {
 	rte_telemetry_init;
 	rte_telemetry_legacy_register;
 	rte_telemetry_register_cmd;
+	rte_tel_get_data_size;
 
 	local: *;
 };
diff --git a/lib/librte_telemetry/telemetry.c b/lib/librte_telemetry/telemetry.c
index 7b6f8a79e..b3a5f4296 100644
--- a/lib/librte_telemetry/telemetry.c
+++ b/lib/librte_telemetry/telemetry.c
@@ -47,6 +47,12 @@ static int num_callbacks; /* How many commands are registered */
 /* Used when accessing or modifying list of command callbacks */
 static rte_spinlock_t callback_sl = RTE_SPINLOCK_INITIALIZER;
 
+size_t
+rte_tel_get_data_size(void)
+{
+	return DATA_STRUCT_SIZE;
+}
+
 int
 rte_telemetry_register_cmd(const char *cmd, telemetry_cb fn, const char *help)
 {
@@ -120,6 +126,23 @@ command_help(const char *cmd __rte_unused, const char *params,
 	return 0;
 }
 
+static int
+recursive_data_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len)
+{
+	size_t used = 0;
+	unsigned int i;
+
+	if (d->type != RTE_TEL_ARRAY_U64)
+		return snprintf(out_buf, buf_len, "null");
+
+	used = rte_tel_json_empty_array(out_buf, buf_len, 0);
+	for (i = 0; i < d->data_len; i++)
+		used = rte_tel_json_add_array_u64(out_buf,
+				buf_len, used,
+				d->data.array[i].u64val);
+	return used;
+}
+
 static void
 output_json(const char *cmd, const struct rte_tel_data *d, int s)
 {
@@ -166,6 +189,17 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 						buf_len, used,
 						v->name, v->value.u64val);
 				break;
+			case RTE_TEL_DATA_VAL:
+			{
+				char temp[buf_len];
+				if (recursive_data_json(v->value.dataval,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_obj_json(
+							cb_data_buf,
+							buf_len, used,
+							v->name, temp);
+				free(v->value.dataval);
+			}
 			}
 		}
 		used += prefix_used;
diff --git a/lib/librte_telemetry/telemetry_data.c b/lib/librte_telemetry/telemetry_data.c
index f424bbd48..46ce7f9b0 100644
--- a/lib/librte_telemetry/telemetry_data.c
+++ b/lib/librte_telemetry/telemetry_data.c
@@ -128,3 +128,21 @@ rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
 	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
 }
+
+int
+rte_tel_data_add_dict_data(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val)
+{
+	struct tel_dict_entry *e = &d->data.dict[d->data_len];
+
+	if (d->type != RTE_TEL_DICT)
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES)
+		return -ENOSPC;
+
+	d->data_len++;
+	e->type = RTE_TEL_DATA_VAL;
+	e->value.dataval = val;
+	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
+	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
+}
diff --git a/lib/librte_telemetry/telemetry_data.h b/lib/librte_telemetry/telemetry_data.h
index ff3a371a3..226d961e0 100644
--- a/lib/librte_telemetry/telemetry_data.h
+++ b/lib/librte_telemetry/telemetry_data.h
@@ -8,6 +8,8 @@
 #include <inttypes.h>
 #include "rte_telemetry.h"
 
+#define DATA_STRUCT_SIZE sizeof(struct rte_tel_data)
+
 enum tel_container_types {
 	RTE_TEL_NULL,	      /** null, used as error value */
 	RTE_TEL_STRING,	      /** basic string type, no included data */
@@ -25,6 +27,7 @@ union tel_value {
 	char sval[RTE_TEL_MAX_STRING_LEN];
 	int ival;
 	uint64_t u64val;
+	struct rte_tel_data *dataval;
 };
 
 struct tel_dict_entry {
diff --git a/lib/librte_telemetry/telemetry_json.h b/lib/librte_telemetry/telemetry_json.h
index a2ce4899e..415cfe7c6 100644
--- a/lib/librte_telemetry/telemetry_json.h
+++ b/lib/librte_telemetry/telemetry_json.h
@@ -155,4 +155,21 @@ rte_tel_json_add_obj_str(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/**
+ * Add a new element with raw JSON value to the JSON object stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_obj_json(char *buf, const int len, const int used,
+		const char *name, const char *val)
+{
+	int ret, end = used - 1;
+	if (used <= 2) /* assume empty, since minimum is '{}' */
+		return __json_snprintf(buf, len, "{\"%s\":%s}", name, val);
+
+	ret = __json_snprintf(buf + end, len - end, ",\"%s\":%s}",
+			name, val);
+	return ret == 0 ? used : end + ret;
+}
+
 #endif /*_RTE_TELEMETRY_JSON_H_*/
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [RFC 2/2] ethdev: add basic stats for telemetry
  2020-06-12 10:53 [dpdk-dev] [RFC 0/2] add basic ethdev stats with data object recursion Ciara Power
  2020-06-12 10:53 ` [dpdk-dev] [RFC 1/2] telemetry: support some recursive data objects Ciara Power
@ 2020-06-12 10:53 ` Ciara Power
  2020-06-12 13:10   ` Bruce Richardson
  2020-06-24 13:48 ` [dpdk-dev] [PATCH v2 0/2] add basic ethdev stats with data object recursion Ciara Power
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 44+ messages in thread
From: Ciara Power @ 2020-06-12 10:53 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: keith.wiles, dev, Ciara Power

The ethdev library now registers a telemetry command for basic ethdev
statistics.

An example usage is shown below:

Connecting to /var/run/dpdk/rte/dpdk_telemetry.v2
{"version": "DPDK 20.05.0-rc3", "pid": 14119, "max_output_len": 16384}
--> /ethdev/stats,0
{"/ethdev/stats": {"ipackets": 0, "opackets": 0, "ibytes": 0, "obytes": \
    0, "imissed": 0, "ierrors": 0, "oerrors": 0, "rx_nombuf": 0, \
    "q_ipackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_opackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_ibytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_obytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_errors": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}}

Signed-off-by: Ciara Power <ciara.power@intel.com>
---
 lib/librte_ethdev/rte_ethdev.c | 54 ++++++++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)

diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
index 8e10a6fc3..1a33e41d3 100644
--- a/lib/librte_ethdev/rte_ethdev.c
+++ b/lib/librte_ethdev/rte_ethdev.c
@@ -5215,6 +5215,58 @@ handle_port_list(const char *cmd __rte_unused,
 	return 0;
 }
 
+static void
+add_port_queue_stats(struct rte_tel_data *d, uint64_t *q_stats,
+		const char *stat_name)
+{
+	int q;
+	struct rte_tel_data *q_data = malloc(rte_tel_get_data_size());
+	rte_tel_data_start_array(q_data, RTE_TEL_U64_VAL);
+	for (q = 0; q < RTE_ETHDEV_QUEUE_STAT_CNTRS; q++)
+		rte_tel_data_add_array_u64(q_data, q_stats[q]);
+	rte_tel_data_add_dict_data(d, stat_name, q_data);
+}
+
+static int
+handle_port_stats(const char *cmd __rte_unused,
+		const char *params,
+		struct rte_tel_data *d)
+{
+	struct rte_eth_stats stats;
+	int port_id, ret;
+
+#define ADD_DICT_STAT(s) rte_tel_data_add_dict_u64(d, #s, stats.s)
+
+	if (params == NULL || strlen(params) == 0 || !isdigit(*params))
+		return -1;
+
+	port_id = atoi(params);
+	if (!rte_eth_dev_is_valid_port(port_id))
+		return -1;
+
+	ret = rte_eth_stats_get(port_id, &stats);
+	if (ret < 0)
+		return -1;
+
+	rte_tel_data_start_dict(d);
+	ADD_DICT_STAT(ipackets);
+	ADD_DICT_STAT(opackets);
+	ADD_DICT_STAT(ibytes);
+	ADD_DICT_STAT(obytes);
+	ADD_DICT_STAT(imissed);
+	ADD_DICT_STAT(ierrors);
+	ADD_DICT_STAT(oerrors);
+	ADD_DICT_STAT(rx_nombuf);
+
+	add_port_queue_stats(d, stats.q_ipackets, "q_ipackets");
+	add_port_queue_stats(d, stats.q_opackets, "q_opackets");
+	add_port_queue_stats(d, stats.q_ibytes, "q_ibytes");
+	add_port_queue_stats(d, stats.q_obytes, "q_obytes");
+	add_port_queue_stats(d, stats.q_errors, "q_errors");
+
+	return 0;
+}
+
 static int
 handle_port_xstats(const char *cmd __rte_unused,
 		const char *params,
@@ -5302,6 +5354,8 @@ RTE_INIT(ethdev_init_log)
 		rte_log_set_level(rte_eth_dev_logtype, RTE_LOG_INFO);
 	rte_telemetry_register_cmd("/ethdev/list", handle_port_list,
 			"Returns list of available ethdev ports. Takes no parameters");
+	rte_telemetry_register_cmd("/ethdev/stats", handle_port_stats,
+			"Returns the basic stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/xstats", handle_port_xstats,
 			"Returns the extended stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/link_status",
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [dpdk-dev] [RFC 1/2] telemetry: support some recursive data objects
  2020-06-12 10:53 ` [dpdk-dev] [RFC 1/2] telemetry: support some recursive data objects Ciara Power
@ 2020-06-12 12:58   ` Bruce Richardson
  2020-06-12 13:07   ` Bruce Richardson
  1 sibling, 0 replies; 44+ messages in thread
From: Bruce Richardson @ 2020-06-12 12:58 UTC (permalink / raw)
  To: Ciara Power
  Cc: kevin.laatz, thomas, ferruh.yigit, arybchenko, keith.wiles, dev

On Fri, Jun 12, 2020 at 11:53:43AM +0100, Ciara Power wrote:
> Dict data objects now support uint64_t array data object values.
> Only one level of recursion supported.
> 
> Signed-off-by: Ciara Power <ciara.power@intel.com>
> ---
>  lib/librte_telemetry/rte_telemetry.h          | 27 +++++++++++++++
>  .../rte_telemetry_version.map                 |  2 ++
>  lib/librte_telemetry/telemetry.c              | 34 +++++++++++++++++++
>  lib/librte_telemetry/telemetry_data.c         | 18 ++++++++++
>  lib/librte_telemetry/telemetry_data.h         |  3 ++
>  lib/librte_telemetry/telemetry_json.h         | 17 ++++++++++
>  6 files changed, 101 insertions(+)
> 
> diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
> index 2c3c96cf7..dc18c34d0 100644
> --- a/lib/librte_telemetry/rte_telemetry.h
> +++ b/lib/librte_telemetry/rte_telemetry.h
> @@ -44,6 +44,7 @@ enum rte_tel_value_type {
>  	RTE_TEL_STRING_VAL, /** a string value */
>  	RTE_TEL_INT_VAL,    /** a signed 32-bit int value */
>  	RTE_TEL_U64_VAL,    /** an unsigned 64-bit int value */
> +	RTE_TEL_DATA_VAL,   /** a rte_tel_data pointer value */
>  };
>  
>  /**
> @@ -188,6 +189,22 @@ int
>  rte_tel_data_add_dict_u64(struct rte_tel_data *d,
>  		const char *name, uint64_t val);
>  
> +/**
> + * Add a data object pointer to a dictionary.
> + * The dict must have been started by rte_tel_data_start_dict().
> + *
> + * @param d
> + *   The data structure passed to the callback
> + * @param x
> + *   The data pointer to be returned in the dictionary
> + * @return
> + *   0 on success, negative errno on error
> + */
> +__rte_experimental
> +int
> +rte_tel_data_add_dict_data(struct rte_tel_data *d, const char *name,
> +		struct rte_tel_data *val);
> +
>  /**
>   * This telemetry callback is used when registering a telemetry command.
>   * It handles getting and formatting information to be returned to telemetry
> @@ -253,4 +270,14 @@ int
>  rte_telemetry_init(const char *runtime_dir, rte_cpuset_t *cpuset,
>  		const char **err_str);
>  
> +/**
> + * Get the size of the rte_tel_data struct.
> + *
> + * @return
> + *  size_t of the struct
> + */
> +__rte_experimental
> +size_t
> +rte_tel_get_data_size(void);
> +
Thanks for this work. Thinking about this, the biggest issue I believe is
always going to be the memory management aspect of it. It seems here that
you are providing an API to allow the memory allocation of the additional
object to be done by the callback itself, but that leaves open a problem of
freeing the memory again - how does the telemetry library know what
allocation function was used, so it knows to free it appropriately? This
could be done by function pointer passed in by when adding the data
element, but I think that is clunky.

Perhaps a better approach here might be to instead add APIs for data alloc
and free, so that there is only one way to manage the memory. That would
remove the need for this size function.


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [dpdk-dev] [RFC 1/2] telemetry: support some recursive data objects
  2020-06-12 10:53 ` [dpdk-dev] [RFC 1/2] telemetry: support some recursive data objects Ciara Power
  2020-06-12 12:58   ` Bruce Richardson
@ 2020-06-12 13:07   ` Bruce Richardson
  2020-06-12 13:14     ` Bruce Richardson
  1 sibling, 1 reply; 44+ messages in thread
From: Bruce Richardson @ 2020-06-12 13:07 UTC (permalink / raw)
  To: Ciara Power
  Cc: kevin.laatz, thomas, ferruh.yigit, arybchenko, keith.wiles, dev

On Fri, Jun 12, 2020 at 11:53:43AM +0100, Ciara Power wrote:
> Dict data objects now support uint64_t array data object values.
> Only one level of recursion supported.
> 
> Signed-off-by: Ciara Power <ciara.power@intel.com>
> ---
>  lib/librte_telemetry/rte_telemetry.h          | 27 +++++++++++++++
>  .../rte_telemetry_version.map                 |  2 ++
>  lib/librte_telemetry/telemetry.c              | 34 +++++++++++++++++++
>  lib/librte_telemetry/telemetry_data.c         | 18 ++++++++++
>  lib/librte_telemetry/telemetry_data.h         |  3 ++
>  lib/librte_telemetry/telemetry_json.h         | 17 ++++++++++
>  6 files changed, 101 insertions(+)
> 
> diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
> index 2c3c96cf7..dc18c34d0 100644
> --- a/lib/librte_telemetry/rte_telemetry.h
> +++ b/lib/librte_telemetry/rte_telemetry.h
> @@ -44,6 +44,7 @@ enum rte_tel_value_type {
>  	RTE_TEL_STRING_VAL, /** a string value */
>  	RTE_TEL_INT_VAL,    /** a signed 32-bit int value */
>  	RTE_TEL_U64_VAL,    /** an unsigned 64-bit int value */
> +	RTE_TEL_DATA_VAL,   /** a rte_tel_data pointer value */

Are there plans to allow arrays of this type since it's added to the enum.
Might be worth adding in v2.

>  };
>  
>  /**
> @@ -188,6 +189,22 @@ int
>  rte_tel_data_add_dict_u64(struct rte_tel_data *d,
>  		const char *name, uint64_t val);
>  
> +/**
> + * Add a data object pointer to a dictionary.
> + * The dict must have been started by rte_tel_data_start_dict().
> + *
> + * @param d
> + *   The data structure passed to the callback
> + * @param x
> + *   The data pointer to be returned in the dictionary
> + * @return
> + *   0 on success, negative errno on error
> + */
> +__rte_experimental
> +int
> +rte_tel_data_add_dict_data(struct rte_tel_data *d, const char *name,
> +		struct rte_tel_data *val);
> +
>  /**
>   * This telemetry callback is used when registering a telemetry command.
>   * It handles getting and formatting information to be returned to telemetry
> @@ -253,4 +270,14 @@ int
>  rte_telemetry_init(const char *runtime_dir, rte_cpuset_t *cpuset,
>  		const char **err_str);
>  
> +/**
> + * Get the size of the rte_tel_data struct.
> + *
> + * @return
> + *  size_t of the struct
> + */
> +__rte_experimental
> +size_t
> +rte_tel_get_data_size(void);
> +
>  #endif
> diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map
> index 86433c21d..c5425eff6 100644
> --- a/lib/librte_telemetry/rte_telemetry_version.map
> +++ b/lib/librte_telemetry/rte_telemetry_version.map
> @@ -4,6 +4,7 @@ EXPERIMENTAL {
>  	rte_tel_data_add_array_int;
>  	rte_tel_data_add_array_string;
>  	rte_tel_data_add_array_u64;
> +	rte_tel_data_add_dict_data;
>  	rte_tel_data_add_dict_int;
>  	rte_tel_data_add_dict_string;
>  	rte_tel_data_add_dict_u64;
> @@ -13,6 +14,7 @@ EXPERIMENTAL {
>  	rte_telemetry_init;
>  	rte_telemetry_legacy_register;
>  	rte_telemetry_register_cmd;
> +	rte_tel_get_data_size;
>  
>  	local: *;
>  };
> diff --git a/lib/librte_telemetry/telemetry.c b/lib/librte_telemetry/telemetry.c
> index 7b6f8a79e..b3a5f4296 100644
> --- a/lib/librte_telemetry/telemetry.c
> +++ b/lib/librte_telemetry/telemetry.c
> @@ -47,6 +47,12 @@ static int num_callbacks; /* How many commands are registered */
>  /* Used when accessing or modifying list of command callbacks */
>  static rte_spinlock_t callback_sl = RTE_SPINLOCK_INITIALIZER;
>  
> +size_t
> +rte_tel_get_data_size(void)
> +{
> +	return DATA_STRUCT_SIZE;
> +}
> +
>  int
>  rte_telemetry_register_cmd(const char *cmd, telemetry_cb fn, const char *help)
>  {
> @@ -120,6 +126,23 @@ command_help(const char *cmd __rte_unused, const char *params,
>  	return 0;
>  }
>  
> +static int
> +recursive_data_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len)
> +{
> +	size_t used = 0;
> +	unsigned int i;
> +
> +	if (d->type != RTE_TEL_ARRAY_U64)
> +		return snprintf(out_buf, buf_len, "null");
> +
> +	used = rte_tel_json_empty_array(out_buf, buf_len, 0);
> +	for (i = 0; i < d->data_len; i++)
> +		used = rte_tel_json_add_array_u64(out_buf,
> +				buf_len, used,
> +				d->data.array[i].u64val);
> +	return used;
> +}
> +
>  static void
>  output_json(const char *cmd, const struct rte_tel_data *d, int s)
>  {
> @@ -166,6 +189,17 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
>  						buf_len, used,
>  						v->name, v->value.u64val);
>  				break;
> +			case RTE_TEL_DATA_VAL:
> +			{
> +				char temp[buf_len];
> +				if (recursive_data_json(v->value.dataval,
> +						temp, buf_len) != 0)
> +					used = rte_tel_json_add_obj_json(
> +							cb_data_buf,
> +							buf_len, used,
> +							v->name, temp);
> +				free(v->value.dataval);
> +			}
>  			}
>  		}
>  		used += prefix_used;
> diff --git a/lib/librte_telemetry/telemetry_data.c b/lib/librte_telemetry/telemetry_data.c
> index f424bbd48..46ce7f9b0 100644
> --- a/lib/librte_telemetry/telemetry_data.c
> +++ b/lib/librte_telemetry/telemetry_data.c
> @@ -128,3 +128,21 @@ rte_tel_data_add_dict_u64(struct rte_tel_data *d,
>  	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
>  	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
>  }
> +
> +int
> +rte_tel_data_add_dict_data(struct rte_tel_data *d, const char *name,
> +		struct rte_tel_data *val)
> +{
> +	struct tel_dict_entry *e = &d->data.dict[d->data_len];
> +
> +	if (d->type != RTE_TEL_DICT)
> +		return -EINVAL;
> +	if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES)
> +		return -ENOSPC;
> +
> +	d->data_len++;
> +	e->type = RTE_TEL_DATA_VAL;
> +	e->value.dataval = val;

I think we need some restrictions here and return error if not met.
1) I believe we only plan to support one level of recursion, so therefore
we need to check that the added element does not have any other data values
already hanging off it.
2) Right now this only supports arrays of numbers in the added data, so
that needs to be checked.

> +	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
> +	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
> +}
> diff --git a/lib/librte_telemetry/telemetry_data.h b/lib/librte_telemetry/telemetry_data.h
> index ff3a371a3..226d961e0 100644
> --- a/lib/librte_telemetry/telemetry_data.h
> +++ b/lib/librte_telemetry/telemetry_data.h
> @@ -8,6 +8,8 @@
>  #include <inttypes.h>
>  #include "rte_telemetry.h"
>  
> +#define DATA_STRUCT_SIZE sizeof(struct rte_tel_data)
> +
>  enum tel_container_types {
>  	RTE_TEL_NULL,	      /** null, used as error value */
>  	RTE_TEL_STRING,	      /** basic string type, no included data */
> @@ -25,6 +27,7 @@ union tel_value {
>  	char sval[RTE_TEL_MAX_STRING_LEN];
>  	int ival;
>  	uint64_t u64val;
> +	struct rte_tel_data *dataval;
>  };
>  
>  struct tel_dict_entry {
> diff --git a/lib/librte_telemetry/telemetry_json.h b/lib/librte_telemetry/telemetry_json.h
> index a2ce4899e..415cfe7c6 100644
> --- a/lib/librte_telemetry/telemetry_json.h
> +++ b/lib/librte_telemetry/telemetry_json.h
> @@ -155,4 +155,21 @@ rte_tel_json_add_obj_str(char *buf, const int len, const int used,
>  	return ret == 0 ? used : end + ret;
>  }
>  
> +/**
> + * Add a new element with raw JSON value to the JSON object stored in the
> + * provided buffer.
> + */
> +static inline int
> +rte_tel_json_add_obj_json(char *buf, const int len, const int used,
> +		const char *name, const char *val)
> +{
> +	int ret, end = used - 1;
> +	if (used <= 2) /* assume empty, since minimum is '{}' */
> +		return __json_snprintf(buf, len, "{\"%s\":%s}", name, val);
> +
> +	ret = __json_snprintf(buf + end, len - end, ",\"%s\":%s}",
> +			name, val);
> +	return ret == 0 ? used : end + ret;
> +}
> +
>  #endif /*_RTE_TELEMETRY_JSON_H_*/
> -- 
> 2.17.1
> 

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [dpdk-dev] [RFC 2/2] ethdev: add basic stats for telemetry
  2020-06-12 10:53 ` [dpdk-dev] [RFC 2/2] ethdev: add basic stats for telemetry Ciara Power
@ 2020-06-12 13:10   ` Bruce Richardson
  0 siblings, 0 replies; 44+ messages in thread
From: Bruce Richardson @ 2020-06-12 13:10 UTC (permalink / raw)
  To: Ciara Power
  Cc: kevin.laatz, thomas, ferruh.yigit, arybchenko, keith.wiles, dev

On Fri, Jun 12, 2020 at 11:53:44AM +0100, Ciara Power wrote:
> The ethdev library now registers a telemetry command for basic ethdev
> statistics.
> 
> An example usage is shown below:
> 
> Connecting to /var/run/dpdk/rte/dpdk_telemetry.v2
> {"version": "DPDK 20.05.0-rc3", "pid": 14119, "max_output_len": 16384}
> --> /ethdev/stats,0
> {"/ethdev/stats": {"ipackets": 0, "opackets": 0, "ibytes": 0, "obytes": \
>     0, "imissed": 0, "ierrors": 0, "oerrors": 0, "rx_nombuf": 0, \
>     "q_ipackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
>     "q_opackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
>     "q_ibytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
>     "q_obytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
>     "q_errors": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}}
> 
> Signed-off-by: Ciara Power <ciara.power@intel.com>
> ---
>  lib/librte_ethdev/rte_ethdev.c | 54 ++++++++++++++++++++++++++++++++++
>  1 file changed, 54 insertions(+)
> 
> diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
> index 8e10a6fc3..1a33e41d3 100644
> --- a/lib/librte_ethdev/rte_ethdev.c
> +++ b/lib/librte_ethdev/rte_ethdev.c
> @@ -5215,6 +5215,58 @@ handle_port_list(const char *cmd __rte_unused,
>  	return 0;
>  }
>  
> +static void
> +add_port_queue_stats(struct rte_tel_data *d, uint64_t *q_stats,
> +		const char *stat_name)
> +{
> +	int q;
> +	struct rte_tel_data *q_data = malloc(rte_tel_get_data_size());
> +	rte_tel_data_start_array(q_data, RTE_TEL_U64_VAL);
> +	for (q = 0; q < RTE_ETHDEV_QUEUE_STAT_CNTRS; q++)
> +		rte_tel_data_add_array_u64(q_data, q_stats[q]);
> +	rte_tel_data_add_dict_data(d, stat_name, q_data);
> +}

It might be worthwhile adding an function to create an array based off
existing numbers. Save a function call per element.

> +
> +static int
> +handle_port_stats(const char *cmd __rte_unused,
> +		const char *params,
> +		struct rte_tel_data *d)
> +{
> +	struct rte_eth_stats stats;
> +	int port_id, ret;
> +
> +#define ADD_DICT_STAT(s) rte_tel_data_add_dict_u64(d, #s, stats.s)

I think the macro would be better defined just above the function, rather
than inside it. Perhaps just after the opening brace might also work, but I
think it looks wrong being in the middle of the code itself.

> +
> +	if (params == NULL || strlen(params) == 0 || !isdigit(*params))
> +		return -1;
> +
> +	port_id = atoi(params);
> +	if (!rte_eth_dev_is_valid_port(port_id))
> +		return -1;
> +
> +	ret = rte_eth_stats_get(port_id, &stats);
> +	if (ret < 0)
> +		return -1;
> +
> +	rte_tel_data_start_dict(d);
> +	ADD_DICT_STAT(ipackets);
> +	ADD_DICT_STAT(opackets);
> +	ADD_DICT_STAT(ibytes);
> +	ADD_DICT_STAT(obytes);
> +	ADD_DICT_STAT(imissed);
> +	ADD_DICT_STAT(ierrors);
> +	ADD_DICT_STAT(oerrors);
> +	ADD_DICT_STAT(rx_nombuf);
> +
> +	add_port_queue_stats(d, stats.q_ipackets, "q_ipackets");
> +	add_port_queue_stats(d, stats.q_opackets, "q_opackets");
> +	add_port_queue_stats(d, stats.q_ibytes, "q_ibytes");
> +	add_port_queue_stats(d, stats.q_obytes, "q_obytes");
> +	add_port_queue_stats(d, stats.q_errors, "q_errors");
> +
> +	return 0;
> +}
> +
>  static int
>  handle_port_xstats(const char *cmd __rte_unused,
>  		const char *params,
> @@ -5302,6 +5354,8 @@ RTE_INIT(ethdev_init_log)
>  		rte_log_set_level(rte_eth_dev_logtype, RTE_LOG_INFO);
>  	rte_telemetry_register_cmd("/ethdev/list", handle_port_list,
>  			"Returns list of available ethdev ports. Takes no parameters");
> +	rte_telemetry_register_cmd("/ethdev/stats", handle_port_stats,
> +			"Returns the basic stats for a port. Parameters: int port_id");
>  	rte_telemetry_register_cmd("/ethdev/xstats", handle_port_xstats,
>  			"Returns the extended stats for a port. Parameters: int port_id");
>  	rte_telemetry_register_cmd("/ethdev/link_status",
> -- 
> 2.17.1
> 

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [dpdk-dev] [RFC 1/2] telemetry: support some recursive data objects
  2020-06-12 13:07   ` Bruce Richardson
@ 2020-06-12 13:14     ` Bruce Richardson
  0 siblings, 0 replies; 44+ messages in thread
From: Bruce Richardson @ 2020-06-12 13:14 UTC (permalink / raw)
  To: Ciara Power
  Cc: kevin.laatz, thomas, ferruh.yigit, arybchenko, keith.wiles, dev

On Fri, Jun 12, 2020 at 02:07:06PM +0100, Bruce Richardson wrote:
> On Fri, Jun 12, 2020 at 11:53:43AM +0100, Ciara Power wrote:
> > Dict data objects now support uint64_t array data object values.
> > Only one level of recursion supported.
> > 
> > Signed-off-by: Ciara Power <ciara.power@intel.com>
> > ---
<snip>
> >  static void
> >  output_json(const char *cmd, const struct rte_tel_data *d, int s)
> >  {
> > @@ -166,6 +189,17 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
> >  						buf_len, used,
> >  						v->name, v->value.u64val);
> >  				break;
> > +			case RTE_TEL_DATA_VAL:
> > +			{
> > +				char temp[buf_len];
> > +				if (recursive_data_json(v->value.dataval,
> > +						temp, buf_len) != 0)
> > +					used = rte_tel_json_add_obj_json(
> > +							cb_data_buf,
> > +							buf_len, used,
> > +							v->name, temp);
> > +				free(v->value.dataval);

Are there cases where we want to preserve the structure across calls rather
than doing an alloc and free each time?

^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v2 0/2] add basic ethdev stats with data object recursion
  2020-06-12 10:53 [dpdk-dev] [RFC 0/2] add basic ethdev stats with data object recursion Ciara Power
  2020-06-12 10:53 ` [dpdk-dev] [RFC 1/2] telemetry: support some recursive data objects Ciara Power
  2020-06-12 10:53 ` [dpdk-dev] [RFC 2/2] ethdev: add basic stats for telemetry Ciara Power
@ 2020-06-24 13:48 ` Ciara Power
  2020-06-24 13:48   ` [dpdk-dev] [PATCH v2 1/2] telemetry: support array values in data objects Ciara Power
  2020-06-24 13:48   ` [dpdk-dev] [PATCH v2 2/2] ethdev: add basic stats for telemetry Ciara Power
  2020-07-02 10:19 ` [dpdk-dev] [PATCH v3 0/2] add basic ethdev stats with data object recursion Ciara Power
                   ` (6 subsequent siblings)
  9 siblings, 2 replies; 44+ messages in thread
From: Ciara Power @ 2020-06-24 13:48 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Ciara Power

This patchset adds support for basic ethdev statistics in Telemetry.
To do this, recursive data object support is needed to report the queue
statistics in a list. With this patch, an array or dictionary supports
uint64_t, int or string array types, which is used for the ethdev
queue stats.

Ciara Power (2):
  telemetry: support array values in data objects
  ethdev: add basic stats for telemetry

 lib/librte_ethdev/rte_ethdev.c                | 53 +++++++++++++++
 lib/librte_telemetry/rte_telemetry.h          | 67 +++++++++++++++++++
 .../rte_telemetry_version.map                 |  4 ++
 lib/librte_telemetry/telemetry.c              | 56 ++++++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 51 ++++++++++++++
 lib/librte_telemetry/telemetry_data.h         |  7 ++
 lib/librte_telemetry/telemetry_json.h         | 33 +++++++++
 7 files changed, 271 insertions(+)

-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v2 1/2] telemetry: support array values in data objects
  2020-06-24 13:48 ` [dpdk-dev] [PATCH v2 0/2] add basic ethdev stats with data object recursion Ciara Power
@ 2020-06-24 13:48   ` Ciara Power
  2020-06-24 15:09     ` Bruce Richardson
  2020-06-24 15:18     ` Bruce Richardson
  2020-06-24 13:48   ` [dpdk-dev] [PATCH v2 2/2] ethdev: add basic stats for telemetry Ciara Power
  1 sibling, 2 replies; 44+ messages in thread
From: Ciara Power @ 2020-06-24 13:48 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Ciara Power

Arrays and Dicts now support uint64_t, int and string
array values. Only one level of recursion supported.

Signed-off-by: Ciara Power <ciara.power@intel.com>

---
v2:
  - Added support for arrays to have container values.
  - Added support for int and string arrays within dict/array.
  - Added APIs for internal container memory management.
---
 lib/librte_telemetry/rte_telemetry.h          | 67 +++++++++++++++++++
 .../rte_telemetry_version.map                 |  4 ++
 lib/librte_telemetry/telemetry.c              | 56 ++++++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 51 ++++++++++++++
 lib/librte_telemetry/telemetry_data.h         |  7 ++
 lib/librte_telemetry/telemetry_json.h         | 33 +++++++++
 6 files changed, 218 insertions(+)

diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
index eb7f2c917..89aff3429 100644
--- a/lib/librte_telemetry/rte_telemetry.h
+++ b/lib/librte_telemetry/rte_telemetry.h
@@ -44,6 +44,7 @@ enum rte_tel_value_type {
 	RTE_TEL_STRING_VAL, /** a string value */
 	RTE_TEL_INT_VAL,    /** a signed 32-bit int value */
 	RTE_TEL_U64_VAL,    /** an unsigned 64-bit int value */
+	RTE_TEL_CONTAINER, /** a container struct */
 };
 
 /**
@@ -134,6 +135,28 @@ __rte_experimental
 int
 rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x);
 
+/**
+ * Add a container to an array.
+ * The array must have been started by rte_tel_data_start_array() with
+ * RTE_TEL_CONTAINER as the type parameter. The rte_tel_data type
+ * must be an array of type u64/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param val
+ *   The rte_tel_data pointer to be returned in the container.
+ * @param keep
+ *   Integer value to represent if rte_tel_data memory should be freed
+ *    after use.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep);
+
 /**
  * Add a string value to a dictionary.
  * The dict must have been started by rte_tel_data_start_dict().
@@ -188,6 +211,27 @@ int
 rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 		const char *name, uint64_t val);
 
+/**
+ * Add a container to a dictionary.
+ * The dict must have been started by rte_tel_data_start_dict().
+ * The rte_tel_data type must be an array of type u64/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param val
+ *   The rte_tel_data pointer to be returned in the container.
+ * @param keep
+ *   Integer value to represent if rte_tel_data memory should be freed
+ *   after use.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep);
+
 /**
  * This telemetry callback is used when registering a telemetry command.
  * It handles getting and formatting information to be returned to telemetry
@@ -262,4 +306,27 @@ int
 rte_telemetry_init(const char *runtime_dir, rte_cpuset_t *cpuset,
 		const char **err_str);
 
+/**
+ * @internal
+ * Get a data object with memory allocated.
+ *
+ * @return
+ *  Pointer to rte_tel_data.
+ */
+__rte_experimental
+struct rte_tel_data *
+rte_tel_data_alloc(void);
+
+/**
+ * @internal
+ * Free a data object that has memory allocated.
+ *
+ * @param data
+ *  Pointer to rte_tel_data.
+ *.
+ */
+__rte_experimental
+void
+rte_tel_data_free(struct rte_tel_data *data);
+
 #endif
diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map
index 86433c21d..d1dbf8d58 100644
--- a/lib/librte_telemetry/rte_telemetry_version.map
+++ b/lib/librte_telemetry/rte_telemetry_version.map
@@ -1,12 +1,16 @@
 EXPERIMENTAL {
 	global:
 
+	rte_tel_data_add_array_container;
 	rte_tel_data_add_array_int;
 	rte_tel_data_add_array_string;
 	rte_tel_data_add_array_u64;
+	rte_tel_data_add_dict_container;
 	rte_tel_data_add_dict_int;
 	rte_tel_data_add_dict_string;
 	rte_tel_data_add_dict_u64;
+	rte_tel_data_alloc;
+	rte_tel_data_free;
 	rte_tel_data_start_array;
 	rte_tel_data_start_dict;
 	rte_tel_data_string;
diff --git a/lib/librte_telemetry/telemetry.c b/lib/librte_telemetry/telemetry.c
index e7e3d861d..a36f60a6c 100644
--- a/lib/librte_telemetry/telemetry.c
+++ b/lib/librte_telemetry/telemetry.c
@@ -120,6 +120,35 @@ command_help(const char *cmd __rte_unused, const char *params,
 	return 0;
 }
 
+static int
+recursive_data_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len)
+{
+	size_t used = 0;
+	unsigned int i;
+
+	if (d->type != RTE_TEL_ARRAY_U64 && d->type != RTE_TEL_ARRAY_INT
+			&& d->type != RTE_TEL_ARRAY_STRING)
+		return snprintf(out_buf, buf_len, "null");
+
+	used = rte_tel_json_empty_array(out_buf, buf_len, 0);
+	if (d->type == RTE_TEL_ARRAY_U64)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_u64(out_buf,
+				buf_len, used,
+				d->data.array[i].u64val);
+	if (d->type == RTE_TEL_ARRAY_INT)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_int(out_buf,
+				buf_len, used,
+				d->data.array[i].ival);
+	if (d->type == RTE_TEL_ARRAY_STRING)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_string(out_buf,
+				buf_len, used,
+				d->data.array[i].sval);
+	return used;
+}
+
 static void
 output_json(const char *cmd, const struct rte_tel_data *d, int s)
 {
@@ -166,6 +195,20 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 						buf_len, used,
 						v->name, v->value.u64val);
 				break;
+			case RTE_TEL_CONTAINER:
+			{
+				char temp[buf_len];
+				const struct container *cont =
+						&v->value.container;
+				if (recursive_data_json(cont->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_obj_json(
+							cb_data_buf,
+							buf_len, used,
+							v->name, temp);
+				if (!cont->keep)
+					rte_tel_data_free(cont->data);
+			}
 			}
 		}
 		used += prefix_used;
@@ -174,6 +217,7 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 	case RTE_TEL_ARRAY_STRING:
 	case RTE_TEL_ARRAY_INT:
 	case RTE_TEL_ARRAY_U64:
+	case RTE_TEL_ARRAY_CONTAINER:
 		prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
 				MAX_CMD_LEN, cmd);
 		cb_data_buf = &out_buf[prefix_used];
@@ -194,6 +238,18 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 				used = rte_tel_json_add_array_u64(cb_data_buf,
 						buf_len, used,
 						d->data.array[i].u64val);
+			else if (d->type == RTE_TEL_ARRAY_CONTAINER) {
+				char temp[buf_len];
+				const struct container *rec_data =
+						&d->data.array[i].container;
+				if (recursive_data_json(rec_data->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_array_json(
+							cb_data_buf,
+							buf_len, used, temp);
+				if (!rec_data->keep)
+					rte_tel_data_free(rec_data->data);
+			}
 		used += prefix_used;
 		used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
 		break;
diff --git a/lib/librte_telemetry/telemetry_data.c b/lib/librte_telemetry/telemetry_data.c
index f424bbd48..77b0fe09a 100644
--- a/lib/librte_telemetry/telemetry_data.c
+++ b/lib/librte_telemetry/telemetry_data.c
@@ -14,6 +14,7 @@ rte_tel_data_start_array(struct rte_tel_data *d, enum rte_tel_value_type type)
 			RTE_TEL_ARRAY_STRING, /* RTE_TEL_STRING_VAL = 0 */
 			RTE_TEL_ARRAY_INT,    /* RTE_TEL_INT_VAL = 1 */
 			RTE_TEL_ARRAY_U64,    /* RTE_TEL_u64_VAL = 2 */
+			RTE_TEL_ARRAY_CONTAINER, /* RTE_TEL_CONTAINER = 3 */
 	};
 	d->type = array_types[type];
 	d->data_len = 0;
@@ -74,6 +75,23 @@ rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x)
 	return 0;
 }
 
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep)
+{
+	if (d->type != RTE_TEL_ARRAY_CONTAINER ||
+			(val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_ARRAY_ENTRIES)
+		return -ENOSPC;
+
+	d->data.array[d->data_len].container.data = val;
+	d->data.array[d->data_len++].container.keep = !!keep;
+	return 0;
+}
+
 int
 rte_tel_data_add_dict_string(struct rte_tel_data *d, const char *name,
 		const char *val)
@@ -128,3 +146,36 @@ rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
 	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
 }
+
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep)
+{
+	struct tel_dict_entry *e = &d->data.dict[d->data_len];
+
+	if (d->type != RTE_TEL_DICT || (val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES)
+		return -ENOSPC;
+
+	d->data_len++;
+	e->type = RTE_TEL_CONTAINER;
+	e->value.container.data = val;
+	e->value.container.keep = !!keep;
+	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
+	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
+}
+
+struct rte_tel_data *
+rte_tel_data_alloc(void)
+{
+	return malloc(sizeof(struct rte_tel_data));
+}
+
+void
+rte_tel_data_free(struct rte_tel_data *data)
+{
+	free(data);
+}
diff --git a/lib/librte_telemetry/telemetry_data.h b/lib/librte_telemetry/telemetry_data.h
index ff3a371a3..adb84a09f 100644
--- a/lib/librte_telemetry/telemetry_data.h
+++ b/lib/librte_telemetry/telemetry_data.h
@@ -15,6 +15,12 @@ enum tel_container_types {
 	RTE_TEL_ARRAY_STRING, /** array of string values only */
 	RTE_TEL_ARRAY_INT,    /** array of signed, 32-bit int values */
 	RTE_TEL_ARRAY_U64,    /** array of unsigned 64-bit int values */
+	RTE_TEL_ARRAY_CONTAINER, /** array of container structs */
+};
+
+struct container {
+	struct rte_tel_data *data;
+	int keep;
 };
 
 /* each type here must have an equivalent enum in the value types enum in
@@ -25,6 +31,7 @@ union tel_value {
 	char sval[RTE_TEL_MAX_STRING_LEN];
 	int ival;
 	uint64_t u64val;
+	struct container container;
 };
 
 struct tel_dict_entry {
diff --git a/lib/librte_telemetry/telemetry_json.h b/lib/librte_telemetry/telemetry_json.h
index a2ce4899e..ad270b9b3 100644
--- a/lib/librte_telemetry/telemetry_json.h
+++ b/lib/librte_telemetry/telemetry_json.h
@@ -102,6 +102,22 @@ rte_tel_json_add_array_u64(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/*
+ * Add a new element with raw JSON value to the JSON array stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_array_json(char *buf, const int len, const int used,
+		const char *str)
+{
+	int ret, end = used - 1; /* strip off final delimiter */
+	if (used <= 2) /* assume empty, since minimum is '[]' */
+		return __json_snprintf(buf, len, "[%s]", str);
+
+	ret = __json_snprintf(buf + end, len - end, ",%s]", str);
+	return ret == 0 ? used : end + ret;
+}
+
 /**
  * Add a new element with uint64_t value to the JSON object stored in the
  * provided buffer.
@@ -155,4 +171,21 @@ rte_tel_json_add_obj_str(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/**
+ * Add a new element with raw JSON value to the JSON object stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_obj_json(char *buf, const int len, const int used,
+		const char *name, const char *val)
+{
+	int ret, end = used - 1;
+	if (used <= 2) /* assume empty, since minimum is '{}' */
+		return __json_snprintf(buf, len, "{\"%s\":%s}", name, val);
+
+	ret = __json_snprintf(buf + end, len - end, ",\"%s\":%s}",
+			name, val);
+	return ret == 0 ? used : end + ret;
+}
+
 #endif /*_RTE_TELEMETRY_JSON_H_*/
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v2 2/2] ethdev: add basic stats for telemetry
  2020-06-24 13:48 ` [dpdk-dev] [PATCH v2 0/2] add basic ethdev stats with data object recursion Ciara Power
  2020-06-24 13:48   ` [dpdk-dev] [PATCH v2 1/2] telemetry: support array values in data objects Ciara Power
@ 2020-06-24 13:48   ` Ciara Power
  2020-06-24 15:19     ` Bruce Richardson
  1 sibling, 1 reply; 44+ messages in thread
From: Ciara Power @ 2020-06-24 13:48 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Ciara Power

The ethdev library now registers a telemetry command for basic ethdev
statistics.

An example usage is shown below:

Connecting to /var/run/dpdk/rte/dpdk_telemetry.v2
{"version": "DPDK 20.08.0-rc0", "pid": 14119, "max_output_len": 16384}
--> /ethdev/stats,0
{"/ethdev/stats": {"ipackets": 0, "opackets": 0, "ibytes": 0, "obytes": \
    0, "imissed": 0, "ierrors": 0, "oerrors": 0, "rx_nombuf": 0, \
    "q_ipackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_opackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_ibytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_obytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_errors": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}}

Signed-off-by: Ciara Power <ciara.power@intel.com>

---
v2:
  - Updated to use memory management APIs.
---
 lib/librte_ethdev/rte_ethdev.c | 53 ++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
index 8e10a6fc3..23de93b50 100644
--- a/lib/librte_ethdev/rte_ethdev.c
+++ b/lib/librte_ethdev/rte_ethdev.c
@@ -5215,6 +5215,57 @@ handle_port_list(const char *cmd __rte_unused,
 	return 0;
 }
 
+static void
+add_port_queue_stats(struct rte_tel_data *d, uint64_t *q_stats,
+		const char *stat_name)
+{
+	int q;
+	struct rte_tel_data *q_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(q_data, RTE_TEL_U64_VAL);
+	for (q = 0; q < RTE_ETHDEV_QUEUE_STAT_CNTRS; q++)
+		rte_tel_data_add_array_u64(q_data, q_stats[q]);
+	rte_tel_data_add_dict_container(d, stat_name, q_data, 0);
+}
+
+#define ADD_DICT_STAT(stats, s) rte_tel_data_add_dict_u64(d, #s, stats.s)
+
+static int
+handle_port_stats(const char *cmd __rte_unused,
+		const char *params,
+		struct rte_tel_data *d)
+{
+	struct rte_eth_stats stats;
+	int port_id, ret;
+
+	if (params == NULL || strlen(params) == 0 || !isdigit(*params))
+		return -1;
+
+	port_id = atoi(params);
+	if (!rte_eth_dev_is_valid_port(port_id))
+		return -1;
+
+	ret = rte_eth_stats_get(port_id, &stats);
+	if (ret < 0)
+		return -1;
+
+	rte_tel_data_start_dict(d);
+	ADD_DICT_STAT(stats, ipackets);
+	ADD_DICT_STAT(stats, opackets);
+	ADD_DICT_STAT(stats, ibytes);
+	ADD_DICT_STAT(stats, obytes);
+	ADD_DICT_STAT(stats, imissed);
+	ADD_DICT_STAT(stats, ierrors);
+	ADD_DICT_STAT(stats, oerrors);
+	ADD_DICT_STAT(stats, rx_nombuf);
+	add_port_queue_stats(d, stats.q_ipackets, "q_ipackets");
+	add_port_queue_stats(d, stats.q_opackets, "q_opackets");
+	add_port_queue_stats(d, stats.q_ibytes, "q_ibytes");
+	add_port_queue_stats(d, stats.q_obytes, "q_obytes");
+	add_port_queue_stats(d, stats.q_errors, "q_errors");
+
+	return 0;
+}
+
 static int
 handle_port_xstats(const char *cmd __rte_unused,
 		const char *params,
@@ -5302,6 +5353,8 @@ RTE_INIT(ethdev_init_log)
 		rte_log_set_level(rte_eth_dev_logtype, RTE_LOG_INFO);
 	rte_telemetry_register_cmd("/ethdev/list", handle_port_list,
 			"Returns list of available ethdev ports. Takes no parameters");
+	rte_telemetry_register_cmd("/ethdev/stats", handle_port_stats,
+			"Returns the basic stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/xstats", handle_port_xstats,
 			"Returns the extended stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/link_status",
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [dpdk-dev] [PATCH v2 1/2] telemetry: support array values in data objects
  2020-06-24 13:48   ` [dpdk-dev] [PATCH v2 1/2] telemetry: support array values in data objects Ciara Power
@ 2020-06-24 15:09     ` Bruce Richardson
  2020-06-24 15:18     ` Bruce Richardson
  1 sibling, 0 replies; 44+ messages in thread
From: Bruce Richardson @ 2020-06-24 15:09 UTC (permalink / raw)
  To: Ciara Power
  Cc: kevin.laatz, thomas, ferruh.yigit, arybchenko, dev, keith.wiles

On Wed, Jun 24, 2020 at 02:48:23PM +0100, Ciara Power wrote:
> Arrays and Dicts now support uint64_t, int and string
> array values. Only one level of recursion supported.
> 
> Signed-off-by: Ciara Power <ciara.power@intel.com>
> 
> ---
Approach seems good to me, but I think you need more detail in the commit
log for this. I think the first sentence needs to make it clearer that its
arrays of uint64_t and arrays of int which can be supported as well as
arrays of strings. I also think some discussion of why this is necessary is
needed in the commit log as well as an explanation of how the memory alloc
and free for the array objects work.

/Bruce

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [dpdk-dev] [PATCH v2 1/2] telemetry: support array values in data objects
  2020-06-24 13:48   ` [dpdk-dev] [PATCH v2 1/2] telemetry: support array values in data objects Ciara Power
  2020-06-24 15:09     ` Bruce Richardson
@ 2020-06-24 15:18     ` Bruce Richardson
  1 sibling, 0 replies; 44+ messages in thread
From: Bruce Richardson @ 2020-06-24 15:18 UTC (permalink / raw)
  To: Ciara Power
  Cc: kevin.laatz, thomas, ferruh.yigit, arybchenko, dev, keith.wiles

On Wed, Jun 24, 2020 at 02:48:23PM +0100, Ciara Power wrote:
> Arrays and Dicts now support uint64_t, int and string
> array values. Only one level of recursion supported.
> 
> Signed-off-by: Ciara Power <ciara.power@intel.com>
> 

A number of comments inline below. Once fixed you can include my ack on V3.

Acked-by: Bruce Richardson <bruce.richardson@intel.com>

> ---
> v2:
>   - Added support for arrays to have container values.
>   - Added support for int and string arrays within dict/array.
>   - Added APIs for internal container memory management.
> ---
>  lib/librte_telemetry/rte_telemetry.h          | 67 +++++++++++++++++++
>  .../rte_telemetry_version.map                 |  4 ++
>  lib/librte_telemetry/telemetry.c              | 56 ++++++++++++++++
>  lib/librte_telemetry/telemetry_data.c         | 51 ++++++++++++++
>  lib/librte_telemetry/telemetry_data.h         |  7 ++
>  lib/librte_telemetry/telemetry_json.h         | 33 +++++++++
>  6 files changed, 218 insertions(+)
> 
> diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
> index eb7f2c917..89aff3429 100644
> --- a/lib/librte_telemetry/rte_telemetry.h
> +++ b/lib/librte_telemetry/rte_telemetry.h
> @@ -44,6 +44,7 @@ enum rte_tel_value_type {
>  	RTE_TEL_STRING_VAL, /** a string value */
>  	RTE_TEL_INT_VAL,    /** a signed 32-bit int value */
>  	RTE_TEL_U64_VAL,    /** an unsigned 64-bit int value */
> +	RTE_TEL_CONTAINER, /** a container struct */
>  };
>  
>  /**
> @@ -134,6 +135,28 @@ __rte_experimental
>  int
>  rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x);
>  
> +/**
> + * Add a container to an array.

I think you need to explain the term container a bit, as in "an existing
telemetry data array", or similar.

> + * The array must have been started by rte_tel_data_start_array() with
> + * RTE_TEL_CONTAINER as the type parameter. The rte_tel_data type
> + * must be an array of type u64/int/string.
> + *
> + * @param d
> + *   The data structure passed to the callback
> + * @param val
> + *   The rte_tel_data pointer to be returned in the container.

"The pointer to the container to be stored in the array"

> + * @param keep
> + *   Integer value to represent if rte_tel_data memory should be freed
> + *    after use.

This would be clearer as a boolean, or call it a flag. How about:
"Flag to indicate that the container memory should not be automatically
freed by the telemetry library once it has finished with the data"

> + *   1 = keep, 0 = free.
> + * @return
> + *   0 on success, negative errno on error
> + */
> +__rte_experimental
> +int
> +rte_tel_data_add_array_container(struct rte_tel_data *d,
> +		struct rte_tel_data *val, int keep);
> +
>  /**
>   * Add a string value to a dictionary.
>   * The dict must have been started by rte_tel_data_start_dict().
> @@ -188,6 +211,27 @@ int
>  rte_tel_data_add_dict_u64(struct rte_tel_data *d,
>  		const char *name, uint64_t val);
>  
> +/**
> + * Add a container to a dictionary.
> + * The dict must have been started by rte_tel_data_start_dict().
> + * The rte_tel_data type must be an array of type u64/int/string.
> + *
> + * @param d
> + *   The data structure passed to the callback
> + * @param val
> + *   The rte_tel_data pointer to be returned in the container.
> + * @param keep
> + *   Integer value to represent if rte_tel_data memory should be freed
> + *   after use.
> + *   1 = keep, 0 = free.
> + * @return
> + *   0 on success, negative errno on error
> + */

Similar comments to the above here.

> +__rte_experimental
> +int
> +rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
> +		struct rte_tel_data *val, int keep);
> +
>  /**
>   * This telemetry callback is used when registering a telemetry command.
>   * It handles getting and formatting information to be returned to telemetry
> @@ -262,4 +306,27 @@ int
>  rte_telemetry_init(const char *runtime_dir, rte_cpuset_t *cpuset,
>  		const char **err_str);
>  
> +/**
> + * @internal
> + * Get a data object with memory allocated.
> + *

Do we want to use the term container here also?

> + * @return
> + *  Pointer to rte_tel_data.
> + */
> +__rte_experimental
> +struct rte_tel_data *
> +rte_tel_data_alloc(void);
> +
> +/**
> + * @internal
> + * Free a data object that has memory allocated.
> + *
> + * @param data
> + *  Pointer to rte_tel_data.
> + *.
> + */
> +__rte_experimental
> +void
> +rte_tel_data_free(struct rte_tel_data *data);
> +
>  #endif
> diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map
> index 86433c21d..d1dbf8d58 100644
> --- a/lib/librte_telemetry/rte_telemetry_version.map
> +++ b/lib/librte_telemetry/rte_telemetry_version.map
> @@ -1,12 +1,16 @@
>  EXPERIMENTAL {
>  	global:
>  
> +	rte_tel_data_add_array_container;
>  	rte_tel_data_add_array_int;
>  	rte_tel_data_add_array_string;
>  	rte_tel_data_add_array_u64;
> +	rte_tel_data_add_dict_container;
>  	rte_tel_data_add_dict_int;
>  	rte_tel_data_add_dict_string;
>  	rte_tel_data_add_dict_u64;
> +	rte_tel_data_alloc;
> +	rte_tel_data_free;
>  	rte_tel_data_start_array;
>  	rte_tel_data_start_dict;
>  	rte_tel_data_string;
> diff --git a/lib/librte_telemetry/telemetry.c b/lib/librte_telemetry/telemetry.c
> index e7e3d861d..a36f60a6c 100644
> --- a/lib/librte_telemetry/telemetry.c
> +++ b/lib/librte_telemetry/telemetry.c
> @@ -120,6 +120,35 @@ command_help(const char *cmd __rte_unused, const char *params,
>  	return 0;
>  }
>  
> +static int
> +recursive_data_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len)

Not sure recursive in the name is great. Do we want to call this
"container_to_json" or "array_to_json" perhaps?

> +{
> +	size_t used = 0;
> +	unsigned int i;
> +
> +	if (d->type != RTE_TEL_ARRAY_U64 && d->type != RTE_TEL_ARRAY_INT
> +			&& d->type != RTE_TEL_ARRAY_STRING)
> +		return snprintf(out_buf, buf_len, "null");
> +
> +	used = rte_tel_json_empty_array(out_buf, buf_len, 0);
> +	if (d->type == RTE_TEL_ARRAY_U64)
> +		for (i = 0; i < d->data_len; i++)
> +			used = rte_tel_json_add_array_u64(out_buf,
> +				buf_len, used,
> +				d->data.array[i].u64val);
> +	if (d->type == RTE_TEL_ARRAY_INT)
> +		for (i = 0; i < d->data_len; i++)
> +			used = rte_tel_json_add_array_int(out_buf,
> +				buf_len, used,
> +				d->data.array[i].ival);
> +	if (d->type == RTE_TEL_ARRAY_STRING)
> +		for (i = 0; i < d->data_len; i++)
> +			used = rte_tel_json_add_array_string(out_buf,
> +				buf_len, used,
> +				d->data.array[i].sval);
> +	return used;
> +}
> +
>  static void
>  output_json(const char *cmd, const struct rte_tel_data *d, int s)
>  {
> @@ -166,6 +195,20 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
>  						buf_len, used,
>  						v->name, v->value.u64val);
>  				break;
> +			case RTE_TEL_CONTAINER:
> +			{
> +				char temp[buf_len];
> +				const struct container *cont =
> +						&v->value.container;
> +				if (recursive_data_json(cont->data,
> +						temp, buf_len) != 0)
> +					used = rte_tel_json_add_obj_json(
> +							cb_data_buf,
> +							buf_len, used,
> +							v->name, temp);
> +				if (!cont->keep)
> +					rte_tel_data_free(cont->data);
> +			}
>  			}
>  		}
>  		used += prefix_used;
> @@ -174,6 +217,7 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
>  	case RTE_TEL_ARRAY_STRING:
>  	case RTE_TEL_ARRAY_INT:
>  	case RTE_TEL_ARRAY_U64:
> +	case RTE_TEL_ARRAY_CONTAINER:
>  		prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
>  				MAX_CMD_LEN, cmd);
>  		cb_data_buf = &out_buf[prefix_used];
> @@ -194,6 +238,18 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
>  				used = rte_tel_json_add_array_u64(cb_data_buf,
>  						buf_len, used,
>  						d->data.array[i].u64val);
> +			else if (d->type == RTE_TEL_ARRAY_CONTAINER) {
> +				char temp[buf_len];
> +				const struct container *rec_data =
> +						&d->data.array[i].container;
> +				if (recursive_data_json(rec_data->data,
> +						temp, buf_len) != 0)
> +					used = rte_tel_json_add_array_json(
> +							cb_data_buf,
> +							buf_len, used, temp);
> +				if (!rec_data->keep)
> +					rte_tel_data_free(rec_data->data);
> +			}
>  		used += prefix_used;
>  		used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
>  		break;
> diff --git a/lib/librte_telemetry/telemetry_data.c b/lib/librte_telemetry/telemetry_data.c
> index f424bbd48..77b0fe09a 100644
> --- a/lib/librte_telemetry/telemetry_data.c
> +++ b/lib/librte_telemetry/telemetry_data.c
> @@ -14,6 +14,7 @@ rte_tel_data_start_array(struct rte_tel_data *d, enum rte_tel_value_type type)
>  			RTE_TEL_ARRAY_STRING, /* RTE_TEL_STRING_VAL = 0 */
>  			RTE_TEL_ARRAY_INT,    /* RTE_TEL_INT_VAL = 1 */
>  			RTE_TEL_ARRAY_U64,    /* RTE_TEL_u64_VAL = 2 */
> +			RTE_TEL_ARRAY_CONTAINER, /* RTE_TEL_CONTAINER = 3 */
>  	};
>  	d->type = array_types[type];
>  	d->data_len = 0;
> @@ -74,6 +75,23 @@ rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x)
>  	return 0;
>  }
>  
> +int
> +rte_tel_data_add_array_container(struct rte_tel_data *d,
> +		struct rte_tel_data *val, int keep)
> +{
> +	if (d->type != RTE_TEL_ARRAY_CONTAINER ||
> +			(val->type != RTE_TEL_ARRAY_U64
> +			&& val->type != RTE_TEL_ARRAY_INT
> +			&& val->type != RTE_TEL_ARRAY_STRING))
> +		return -EINVAL;
> +	if (d->data_len >= RTE_TEL_MAX_ARRAY_ENTRIES)
> +		return -ENOSPC;
> +
> +	d->data.array[d->data_len].container.data = val;
> +	d->data.array[d->data_len++].container.keep = !!keep;
> +	return 0;
> +}
> +
>  int
>  rte_tel_data_add_dict_string(struct rte_tel_data *d, const char *name,
>  		const char *val)
> @@ -128,3 +146,36 @@ rte_tel_data_add_dict_u64(struct rte_tel_data *d,
>  	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
>  	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
>  }
> +
> +int
> +rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
> +		struct rte_tel_data *val, int keep)
> +{
> +	struct tel_dict_entry *e = &d->data.dict[d->data_len];
> +
> +	if (d->type != RTE_TEL_DICT || (val->type != RTE_TEL_ARRAY_U64
> +			&& val->type != RTE_TEL_ARRAY_INT
> +			&& val->type != RTE_TEL_ARRAY_STRING))
> +		return -EINVAL;
> +	if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES)
> +		return -ENOSPC;
> +
> +	d->data_len++;
> +	e->type = RTE_TEL_CONTAINER;
> +	e->value.container.data = val;
> +	e->value.container.keep = !!keep;
> +	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
> +	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
> +}
> +
> +struct rte_tel_data *
> +rte_tel_data_alloc(void)
> +{
> +	return malloc(sizeof(struct rte_tel_data));
> +}
> +
> +void
> +rte_tel_data_free(struct rte_tel_data *data)
> +{
> +	free(data);
> +}
> diff --git a/lib/librte_telemetry/telemetry_data.h b/lib/librte_telemetry/telemetry_data.h
> index ff3a371a3..adb84a09f 100644
> --- a/lib/librte_telemetry/telemetry_data.h
> +++ b/lib/librte_telemetry/telemetry_data.h
> @@ -15,6 +15,12 @@ enum tel_container_types {
>  	RTE_TEL_ARRAY_STRING, /** array of string values only */
>  	RTE_TEL_ARRAY_INT,    /** array of signed, 32-bit int values */
>  	RTE_TEL_ARRAY_U64,    /** array of unsigned 64-bit int values */
> +	RTE_TEL_ARRAY_CONTAINER, /** array of container structs */
> +};
> +
> +struct container {
> +	struct rte_tel_data *data;
> +	int keep;
>  };
>  
>  /* each type here must have an equivalent enum in the value types enum in
> @@ -25,6 +31,7 @@ union tel_value {
>  	char sval[RTE_TEL_MAX_STRING_LEN];
>  	int ival;
>  	uint64_t u64val;
> +	struct container container;
>  };
>  
>  struct tel_dict_entry {
> diff --git a/lib/librte_telemetry/telemetry_json.h b/lib/librte_telemetry/telemetry_json.h
> index a2ce4899e..ad270b9b3 100644
> --- a/lib/librte_telemetry/telemetry_json.h
> +++ b/lib/librte_telemetry/telemetry_json.h
> @@ -102,6 +102,22 @@ rte_tel_json_add_array_u64(char *buf, const int len, const int used,
>  	return ret == 0 ? used : end + ret;
>  }
>  
> +/*
> + * Add a new element with raw JSON value to the JSON array stored in the
> + * provided buffer.
> + */
> +static inline int
> +rte_tel_json_add_array_json(char *buf, const int len, const int used,
> +		const char *str)
> +{
> +	int ret, end = used - 1; /* strip off final delimiter */
> +	if (used <= 2) /* assume empty, since minimum is '[]' */
> +		return __json_snprintf(buf, len, "[%s]", str);
> +
> +	ret = __json_snprintf(buf + end, len - end, ",%s]", str);
> +	return ret == 0 ? used : end + ret;
> +}
> +
>  /**
>   * Add a new element with uint64_t value to the JSON object stored in the
>   * provided buffer.
> @@ -155,4 +171,21 @@ rte_tel_json_add_obj_str(char *buf, const int len, const int used,
>  	return ret == 0 ? used : end + ret;
>  }
>  
> +/**
> + * Add a new element with raw JSON value to the JSON object stored in the
> + * provided buffer.
> + */
> +static inline int
> +rte_tel_json_add_obj_json(char *buf, const int len, const int used,
> +		const char *name, const char *val)
> +{
> +	int ret, end = used - 1;
> +	if (used <= 2) /* assume empty, since minimum is '{}' */
> +		return __json_snprintf(buf, len, "{\"%s\":%s}", name, val);
> +
> +	ret = __json_snprintf(buf + end, len - end, ",\"%s\":%s}",
> +			name, val);
> +	return ret == 0 ? used : end + ret;
> +}
> +
>  #endif /*_RTE_TELEMETRY_JSON_H_*/
> -- 
> 2.17.1
> 

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [dpdk-dev] [PATCH v2 2/2] ethdev: add basic stats for telemetry
  2020-06-24 13:48   ` [dpdk-dev] [PATCH v2 2/2] ethdev: add basic stats for telemetry Ciara Power
@ 2020-06-24 15:19     ` Bruce Richardson
  0 siblings, 0 replies; 44+ messages in thread
From: Bruce Richardson @ 2020-06-24 15:19 UTC (permalink / raw)
  To: Ciara Power
  Cc: kevin.laatz, thomas, ferruh.yigit, arybchenko, dev, keith.wiles

On Wed, Jun 24, 2020 at 02:48:24PM +0100, Ciara Power wrote:
> The ethdev library now registers a telemetry command for basic ethdev
> statistics.
> 
> An example usage is shown below:
> 
> Connecting to /var/run/dpdk/rte/dpdk_telemetry.v2
> {"version": "DPDK 20.08.0-rc0", "pid": 14119, "max_output_len": 16384}
> --> /ethdev/stats,0
> {"/ethdev/stats": {"ipackets": 0, "opackets": 0, "ibytes": 0, "obytes": \
>     0, "imissed": 0, "ierrors": 0, "oerrors": 0, "rx_nombuf": 0, \
>     "q_ipackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
>     "q_opackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
>     "q_ibytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
>     "q_obytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
>     "q_errors": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}}
> 
> Signed-off-by: Ciara Power <ciara.power@intel.com>
> 
One comment inline below, otherwise:

Acked-by: Bruce Richardson <bruce.richardson@intel.com>

> ---
> v2:
>   - Updated to use memory management APIs.
> ---
>  lib/librte_ethdev/rte_ethdev.c | 53 ++++++++++++++++++++++++++++++++++
>  1 file changed, 53 insertions(+)
> 
> diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
> index 8e10a6fc3..23de93b50 100644
> --- a/lib/librte_ethdev/rte_ethdev.c
> +++ b/lib/librte_ethdev/rte_ethdev.c
> @@ -5215,6 +5215,57 @@ handle_port_list(const char *cmd __rte_unused,
>  	return 0;
>  }
>  
> +static void
> +add_port_queue_stats(struct rte_tel_data *d, uint64_t *q_stats,
> +		const char *stat_name)
> +{
> +	int q;
> +	struct rte_tel_data *q_data = rte_tel_data_alloc();
> +	rte_tel_data_start_array(q_data, RTE_TEL_U64_VAL);
> +	for (q = 0; q < RTE_ETHDEV_QUEUE_STAT_CNTRS; q++)
> +		rte_tel_data_add_array_u64(q_data, q_stats[q]);
> +	rte_tel_data_add_dict_container(d, stat_name, q_data, 0);
> +}
> +
> +#define ADD_DICT_STAT(stats, s) rte_tel_data_add_dict_u64(d, #s, stats.s)
> +
> +static int
> +handle_port_stats(const char *cmd __rte_unused,
> +		const char *params,
> +		struct rte_tel_data *d)
> +{
> +	struct rte_eth_stats stats;
> +	int port_id, ret;
> +
> +	if (params == NULL || strlen(params) == 0 || !isdigit(*params))
> +		return -1;
> +
> +	port_id = atoi(params);
> +	if (!rte_eth_dev_is_valid_port(port_id))
> +		return -1;
> +
> +	ret = rte_eth_stats_get(port_id, &stats);
> +	if (ret < 0)
> +		return -1;
> +
> +	rte_tel_data_start_dict(d);
> +	ADD_DICT_STAT(stats, ipackets);
> +	ADD_DICT_STAT(stats, opackets);
> +	ADD_DICT_STAT(stats, ibytes);
> +	ADD_DICT_STAT(stats, obytes);
> +	ADD_DICT_STAT(stats, imissed);
> +	ADD_DICT_STAT(stats, ierrors);
> +	ADD_DICT_STAT(stats, oerrors);
> +	ADD_DICT_STAT(stats, rx_nombuf);
> +	add_port_queue_stats(d, stats.q_ipackets, "q_ipackets");
> +	add_port_queue_stats(d, stats.q_opackets, "q_opackets");
> +	add_port_queue_stats(d, stats.q_ibytes, "q_ibytes");
> +	add_port_queue_stats(d, stats.q_obytes, "q_obytes");
> +	add_port_queue_stats(d, stats.q_errors, "q_errors");
> +
> +	return 0;
> +}
> +
>  static int
>  handle_port_xstats(const char *cmd __rte_unused,
>  		const char *params,
> @@ -5302,6 +5353,8 @@ RTE_INIT(ethdev_init_log)
>  		rte_log_set_level(rte_eth_dev_logtype, RTE_LOG_INFO);
>  	rte_telemetry_register_cmd("/ethdev/list", handle_port_list,
>  			"Returns list of available ethdev ports. Takes no parameters");
> +	rte_telemetry_register_cmd("/ethdev/stats", handle_port_stats,
> +			"Returns the basic stats for a port. Parameters: int port_id");

I think "common stats" or "standard stats" may be better than calling them
"basic stats" since the main advantage of them is that they are
common/standard across all drivers and are not NIC-specific.

>  	rte_telemetry_register_cmd("/ethdev/xstats", handle_port_xstats,
>  			"Returns the extended stats for a port. Parameters: int port_id");
>  	rte_telemetry_register_cmd("/ethdev/link_status",
> -- 
> 2.17.1
> 

^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v3 0/2] add basic ethdev stats with data object recursion
  2020-06-12 10:53 [dpdk-dev] [RFC 0/2] add basic ethdev stats with data object recursion Ciara Power
                   ` (2 preceding siblings ...)
  2020-06-24 13:48 ` [dpdk-dev] [PATCH v2 0/2] add basic ethdev stats with data object recursion Ciara Power
@ 2020-07-02 10:19 ` Ciara Power
  2020-07-02 10:19   ` [dpdk-dev] [PATCH v3 1/2] telemetry: support array values in data objects Ciara Power
  2020-07-02 10:19   ` [dpdk-dev] [PATCH v3 2/2] ethdev: add common stats for telemetry Ciara Power
  2020-07-13 14:23 ` [dpdk-dev] [PATCH v4 0/2] add basic ethdev stats with data object recursion Ciara Power
                   ` (5 subsequent siblings)
  9 siblings, 2 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-02 10:19 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Ciara Power

This patchset adds support for basic ethdev statistics in Telemetry.
To do this, recursive data object support is needed to report the queue
statistics in a list. With this patch, an array or dictionary supports
uint64_t, int or string array type values, which is used for the ethdev
queue stats.

Ciara Power (2):
  telemetry: support array values in data objects
  ethdev: add common stats for telemetry

 lib/librte_ethdev/rte_ethdev.c                | 53 +++++++++++++++
 lib/librte_telemetry/rte_telemetry.h          | 68 +++++++++++++++++++
 .../rte_telemetry_version.map                 |  4 ++
 lib/librte_telemetry/telemetry.c              | 56 +++++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 51 ++++++++++++++
 lib/librte_telemetry/telemetry_data.h         |  7 ++
 lib/librte_telemetry/telemetry_json.h         | 33 +++++++++
 7 files changed, 272 insertions(+)

-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v3 1/2] telemetry: support array values in data objects
  2020-07-02 10:19 ` [dpdk-dev] [PATCH v3 0/2] add basic ethdev stats with data object recursion Ciara Power
@ 2020-07-02 10:19   ` Ciara Power
  2020-07-07  7:15     ` Thomas Monjalon
  2020-07-02 10:19   ` [dpdk-dev] [PATCH v3 2/2] ethdev: add common stats for telemetry Ciara Power
  1 sibling, 1 reply; 44+ messages in thread
From: Ciara Power @ 2020-07-02 10:19 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Ciara Power

Arrays of type uint64_t/int/string can now be included within an array
or dict. One level of embedded containers is supported. This is
necessary to allow for instances such as the ethdev queue stats to be
reported as a list of uint64_t values, rather than having multiple dict
entries with one uint64_t value for each queue stat.

The memory management APIs provided by telemetry simplify the memory
allocation/free aspect of the embedded container. The rte_tel_data_alloc
function is called in the library/app callback to return a pointer to a
container that has been allocated memory. When adding this container
to an array/dict, a parameter is passed to indicate if the memory
should be freed by telemetry after use. This will allow reuse of the
allocated memory if the library/app wishes to do so.

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

---
v3:
  - Modified commit log.
  - Changed function name to reference container.
  - Modified Doxygen comments to reference container.
v2:
  - Added support for arrays to have container values.
  - Added support for int and string arrays within dict/array.
  - Added APIs for internal container memory management.
---
 lib/librte_telemetry/rte_telemetry.h          | 68 +++++++++++++++++++
 .../rte_telemetry_version.map                 |  4 ++
 lib/librte_telemetry/telemetry.c              | 56 +++++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 51 ++++++++++++++
 lib/librte_telemetry/telemetry_data.h         |  7 ++
 lib/librte_telemetry/telemetry_json.h         | 33 +++++++++
 6 files changed, 219 insertions(+)

diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
index eb7f2c917..99e7a7c00 100644
--- a/lib/librte_telemetry/rte_telemetry.h
+++ b/lib/librte_telemetry/rte_telemetry.h
@@ -44,6 +44,7 @@ enum rte_tel_value_type {
 	RTE_TEL_STRING_VAL, /** a string value */
 	RTE_TEL_INT_VAL,    /** a signed 32-bit int value */
 	RTE_TEL_U64_VAL,    /** an unsigned 64-bit int value */
+	RTE_TEL_CONTAINER, /** a container struct */
 };
 
 /**
@@ -134,6 +135,28 @@ __rte_experimental
 int
 rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x);
 
+/**
+ * Add a container to an array. A container is an existing telemetry data
+ * array. The array the container is to be added to must have been started by
+ * rte_tel_data_start_array() with RTE_TEL_CONTAINER as the type parameter.
+ * The container type must be an array of type uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param val
+ *   The pointer to the container to be stored in the array.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep);
+
 /**
  * Add a string value to a dictionary.
  * The dict must have been started by rte_tel_data_start_dict().
@@ -188,6 +211,28 @@ int
 rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 		const char *name, uint64_t val);
 
+/**
+ * Add a container to a dictionary. A container is an existing telemetry data
+ * array. The dict the container is to be added to must have been started by
+ * rte_tel_data_start_dict(). The container must be an array of type
+ * uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param val
+ *   The pointer to the container to be stored in the dict.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep);
+
 /**
  * This telemetry callback is used when registering a telemetry command.
  * It handles getting and formatting information to be returned to telemetry
@@ -262,4 +307,27 @@ int
 rte_telemetry_init(const char *runtime_dir, rte_cpuset_t *cpuset,
 		const char **err_str);
 
+/**
+ * Get a pointer to a container with memory allocated. The container is to be
+ * used embedded within an existing telemetry dict/array.
+ *
+ * @return
+ *  Pointer to a container.
+ */
+__rte_experimental
+struct rte_tel_data *
+rte_tel_data_alloc(void);
+
+/**
+ * @internal
+ * Free a container that has memory allocated.
+ *
+ * @param data
+ *  Pointer to container.
+ *.
+ */
+__rte_experimental
+void
+rte_tel_data_free(struct rte_tel_data *data);
+
 #endif
diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map
index 86433c21d..d1dbf8d58 100644
--- a/lib/librte_telemetry/rte_telemetry_version.map
+++ b/lib/librte_telemetry/rte_telemetry_version.map
@@ -1,12 +1,16 @@
 EXPERIMENTAL {
 	global:
 
+	rte_tel_data_add_array_container;
 	rte_tel_data_add_array_int;
 	rte_tel_data_add_array_string;
 	rte_tel_data_add_array_u64;
+	rte_tel_data_add_dict_container;
 	rte_tel_data_add_dict_int;
 	rte_tel_data_add_dict_string;
 	rte_tel_data_add_dict_u64;
+	rte_tel_data_alloc;
+	rte_tel_data_free;
 	rte_tel_data_start_array;
 	rte_tel_data_start_dict;
 	rte_tel_data_string;
diff --git a/lib/librte_telemetry/telemetry.c b/lib/librte_telemetry/telemetry.c
index e7e3d861d..691b52144 100644
--- a/lib/librte_telemetry/telemetry.c
+++ b/lib/librte_telemetry/telemetry.c
@@ -120,6 +120,35 @@ command_help(const char *cmd __rte_unused, const char *params,
 	return 0;
 }
 
+static int
+container_to_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len)
+{
+	size_t used = 0;
+	unsigned int i;
+
+	if (d->type != RTE_TEL_ARRAY_U64 && d->type != RTE_TEL_ARRAY_INT
+			&& d->type != RTE_TEL_ARRAY_STRING)
+		return snprintf(out_buf, buf_len, "null");
+
+	used = rte_tel_json_empty_array(out_buf, buf_len, 0);
+	if (d->type == RTE_TEL_ARRAY_U64)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_u64(out_buf,
+				buf_len, used,
+				d->data.array[i].u64val);
+	if (d->type == RTE_TEL_ARRAY_INT)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_int(out_buf,
+				buf_len, used,
+				d->data.array[i].ival);
+	if (d->type == RTE_TEL_ARRAY_STRING)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_string(out_buf,
+				buf_len, used,
+				d->data.array[i].sval);
+	return used;
+}
+
 static void
 output_json(const char *cmd, const struct rte_tel_data *d, int s)
 {
@@ -166,6 +195,20 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 						buf_len, used,
 						v->name, v->value.u64val);
 				break;
+			case RTE_TEL_CONTAINER:
+			{
+				char temp[buf_len];
+				const struct container *cont =
+						&v->value.container;
+				if (container_to_json(cont->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_obj_json(
+							cb_data_buf,
+							buf_len, used,
+							v->name, temp);
+				if (!cont->keep)
+					rte_tel_data_free(cont->data);
+			}
 			}
 		}
 		used += prefix_used;
@@ -174,6 +217,7 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 	case RTE_TEL_ARRAY_STRING:
 	case RTE_TEL_ARRAY_INT:
 	case RTE_TEL_ARRAY_U64:
+	case RTE_TEL_ARRAY_CONTAINER:
 		prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
 				MAX_CMD_LEN, cmd);
 		cb_data_buf = &out_buf[prefix_used];
@@ -194,6 +238,18 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 				used = rte_tel_json_add_array_u64(cb_data_buf,
 						buf_len, used,
 						d->data.array[i].u64val);
+			else if (d->type == RTE_TEL_ARRAY_CONTAINER) {
+				char temp[buf_len];
+				const struct container *rec_data =
+						&d->data.array[i].container;
+				if (container_to_json(rec_data->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_array_json(
+							cb_data_buf,
+							buf_len, used, temp);
+				if (!rec_data->keep)
+					rte_tel_data_free(rec_data->data);
+			}
 		used += prefix_used;
 		used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
 		break;
diff --git a/lib/librte_telemetry/telemetry_data.c b/lib/librte_telemetry/telemetry_data.c
index f424bbd48..77b0fe09a 100644
--- a/lib/librte_telemetry/telemetry_data.c
+++ b/lib/librte_telemetry/telemetry_data.c
@@ -14,6 +14,7 @@ rte_tel_data_start_array(struct rte_tel_data *d, enum rte_tel_value_type type)
 			RTE_TEL_ARRAY_STRING, /* RTE_TEL_STRING_VAL = 0 */
 			RTE_TEL_ARRAY_INT,    /* RTE_TEL_INT_VAL = 1 */
 			RTE_TEL_ARRAY_U64,    /* RTE_TEL_u64_VAL = 2 */
+			RTE_TEL_ARRAY_CONTAINER, /* RTE_TEL_CONTAINER = 3 */
 	};
 	d->type = array_types[type];
 	d->data_len = 0;
@@ -74,6 +75,23 @@ rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x)
 	return 0;
 }
 
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep)
+{
+	if (d->type != RTE_TEL_ARRAY_CONTAINER ||
+			(val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_ARRAY_ENTRIES)
+		return -ENOSPC;
+
+	d->data.array[d->data_len].container.data = val;
+	d->data.array[d->data_len++].container.keep = !!keep;
+	return 0;
+}
+
 int
 rte_tel_data_add_dict_string(struct rte_tel_data *d, const char *name,
 		const char *val)
@@ -128,3 +146,36 @@ rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
 	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
 }
+
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep)
+{
+	struct tel_dict_entry *e = &d->data.dict[d->data_len];
+
+	if (d->type != RTE_TEL_DICT || (val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES)
+		return -ENOSPC;
+
+	d->data_len++;
+	e->type = RTE_TEL_CONTAINER;
+	e->value.container.data = val;
+	e->value.container.keep = !!keep;
+	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
+	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
+}
+
+struct rte_tel_data *
+rte_tel_data_alloc(void)
+{
+	return malloc(sizeof(struct rte_tel_data));
+}
+
+void
+rte_tel_data_free(struct rte_tel_data *data)
+{
+	free(data);
+}
diff --git a/lib/librte_telemetry/telemetry_data.h b/lib/librte_telemetry/telemetry_data.h
index ff3a371a3..adb84a09f 100644
--- a/lib/librte_telemetry/telemetry_data.h
+++ b/lib/librte_telemetry/telemetry_data.h
@@ -15,6 +15,12 @@ enum tel_container_types {
 	RTE_TEL_ARRAY_STRING, /** array of string values only */
 	RTE_TEL_ARRAY_INT,    /** array of signed, 32-bit int values */
 	RTE_TEL_ARRAY_U64,    /** array of unsigned 64-bit int values */
+	RTE_TEL_ARRAY_CONTAINER, /** array of container structs */
+};
+
+struct container {
+	struct rte_tel_data *data;
+	int keep;
 };
 
 /* each type here must have an equivalent enum in the value types enum in
@@ -25,6 +31,7 @@ union tel_value {
 	char sval[RTE_TEL_MAX_STRING_LEN];
 	int ival;
 	uint64_t u64val;
+	struct container container;
 };
 
 struct tel_dict_entry {
diff --git a/lib/librte_telemetry/telemetry_json.h b/lib/librte_telemetry/telemetry_json.h
index a2ce4899e..ad270b9b3 100644
--- a/lib/librte_telemetry/telemetry_json.h
+++ b/lib/librte_telemetry/telemetry_json.h
@@ -102,6 +102,22 @@ rte_tel_json_add_array_u64(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/*
+ * Add a new element with raw JSON value to the JSON array stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_array_json(char *buf, const int len, const int used,
+		const char *str)
+{
+	int ret, end = used - 1; /* strip off final delimiter */
+	if (used <= 2) /* assume empty, since minimum is '[]' */
+		return __json_snprintf(buf, len, "[%s]", str);
+
+	ret = __json_snprintf(buf + end, len - end, ",%s]", str);
+	return ret == 0 ? used : end + ret;
+}
+
 /**
  * Add a new element with uint64_t value to the JSON object stored in the
  * provided buffer.
@@ -155,4 +171,21 @@ rte_tel_json_add_obj_str(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/**
+ * Add a new element with raw JSON value to the JSON object stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_obj_json(char *buf, const int len, const int used,
+		const char *name, const char *val)
+{
+	int ret, end = used - 1;
+	if (used <= 2) /* assume empty, since minimum is '{}' */
+		return __json_snprintf(buf, len, "{\"%s\":%s}", name, val);
+
+	ret = __json_snprintf(buf + end, len - end, ",\"%s\":%s}",
+			name, val);
+	return ret == 0 ? used : end + ret;
+}
+
 #endif /*_RTE_TELEMETRY_JSON_H_*/
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v3 2/2] ethdev: add common stats for telemetry
  2020-07-02 10:19 ` [dpdk-dev] [PATCH v3 0/2] add basic ethdev stats with data object recursion Ciara Power
  2020-07-02 10:19   ` [dpdk-dev] [PATCH v3 1/2] telemetry: support array values in data objects Ciara Power
@ 2020-07-02 10:19   ` Ciara Power
  1 sibling, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-02 10:19 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Ciara Power

The ethdev library now registers a telemetry command for common ethdev
statistics.

An example usage is shown below:

Connecting to /var/run/dpdk/rte/dpdk_telemetry.v2
{"version": "DPDK 20.08.0-rc0", "pid": 14119, "max_output_len": 16384}
--> /ethdev/stats,0
{"/ethdev/stats": {"ipackets": 0, "opackets": 0, "ibytes": 0, "obytes": \
    0, "imissed": 0, "ierrors": 0, "oerrors": 0, "rx_nombuf": 0, \
    "q_ipackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_opackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_ibytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_obytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_errors": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}}

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

---
v3:
  - Updated help text and commit subject line.
v2:
  - Updated to use memory management APIs.
---
 lib/librte_ethdev/rte_ethdev.c | 53 ++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
index 8e10a6fc3..63086864f 100644
--- a/lib/librte_ethdev/rte_ethdev.c
+++ b/lib/librte_ethdev/rte_ethdev.c
@@ -5215,6 +5215,57 @@ handle_port_list(const char *cmd __rte_unused,
 	return 0;
 }
 
+static void
+add_port_queue_stats(struct rte_tel_data *d, uint64_t *q_stats,
+		const char *stat_name)
+{
+	int q;
+	struct rte_tel_data *q_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(q_data, RTE_TEL_U64_VAL);
+	for (q = 0; q < RTE_ETHDEV_QUEUE_STAT_CNTRS; q++)
+		rte_tel_data_add_array_u64(q_data, q_stats[q]);
+	rte_tel_data_add_dict_container(d, stat_name, q_data, 0);
+}
+
+#define ADD_DICT_STAT(stats, s) rte_tel_data_add_dict_u64(d, #s, stats.s)
+
+static int
+handle_port_stats(const char *cmd __rte_unused,
+		const char *params,
+		struct rte_tel_data *d)
+{
+	struct rte_eth_stats stats;
+	int port_id, ret;
+
+	if (params == NULL || strlen(params) == 0 || !isdigit(*params))
+		return -1;
+
+	port_id = atoi(params);
+	if (!rte_eth_dev_is_valid_port(port_id))
+		return -1;
+
+	ret = rte_eth_stats_get(port_id, &stats);
+	if (ret < 0)
+		return -1;
+
+	rte_tel_data_start_dict(d);
+	ADD_DICT_STAT(stats, ipackets);
+	ADD_DICT_STAT(stats, opackets);
+	ADD_DICT_STAT(stats, ibytes);
+	ADD_DICT_STAT(stats, obytes);
+	ADD_DICT_STAT(stats, imissed);
+	ADD_DICT_STAT(stats, ierrors);
+	ADD_DICT_STAT(stats, oerrors);
+	ADD_DICT_STAT(stats, rx_nombuf);
+	add_port_queue_stats(d, stats.q_ipackets, "q_ipackets");
+	add_port_queue_stats(d, stats.q_opackets, "q_opackets");
+	add_port_queue_stats(d, stats.q_ibytes, "q_ibytes");
+	add_port_queue_stats(d, stats.q_obytes, "q_obytes");
+	add_port_queue_stats(d, stats.q_errors, "q_errors");
+
+	return 0;
+}
+
 static int
 handle_port_xstats(const char *cmd __rte_unused,
 		const char *params,
@@ -5302,6 +5353,8 @@ RTE_INIT(ethdev_init_log)
 		rte_log_set_level(rte_eth_dev_logtype, RTE_LOG_INFO);
 	rte_telemetry_register_cmd("/ethdev/list", handle_port_list,
 			"Returns list of available ethdev ports. Takes no parameters");
+	rte_telemetry_register_cmd("/ethdev/stats", handle_port_stats,
+			"Returns the common stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/xstats", handle_port_xstats,
 			"Returns the extended stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/link_status",
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [dpdk-dev] [PATCH v3 1/2] telemetry: support array values in data objects
  2020-07-02 10:19   ` [dpdk-dev] [PATCH v3 1/2] telemetry: support array values in data objects Ciara Power
@ 2020-07-07  7:15     ` Thomas Monjalon
  0 siblings, 0 replies; 44+ messages in thread
From: Thomas Monjalon @ 2020-07-07  7:15 UTC (permalink / raw)
  To: Ciara Power; +Cc: kevin.laatz, ferruh.yigit, arybchenko, dev, keith.wiles

02/07/2020 12:19, Ciara Power:
> +/**
> + * Add a container to a dictionary. A container is an existing telemetry data
> + * array. The dict the container is to be added to must have been started by
> + * rte_tel_data_start_dict(). The container must be an array of type
> + * uint64_t/int/string.
> + *
> + * @param d
> + *   The data structure passed to the callback
> + * @param val
> + *   The pointer to the container to be stored in the dict.
> + * @param keep
> + *   Flag to indicate that the container memory should not be automatically
> + *   freed by the telemetry library once it has finished with the data.
> + *   1 = keep, 0 = free.
> + * @return
> + *   0 on success, negative errno on error
> + */
> +__rte_experimental
> +int
> +rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
> +		struct rte_tel_data *val, int keep);

Please fix this miss:

rte_telemetry.h:233: warning: The following parameter of rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name, struct rte_tel_data *val, int keep) is not documented:
+  parameter 'name'




^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v4 0/2] add basic ethdev stats with data object recursion
  2020-06-12 10:53 [dpdk-dev] [RFC 0/2] add basic ethdev stats with data object recursion Ciara Power
                   ` (3 preceding siblings ...)
  2020-07-02 10:19 ` [dpdk-dev] [PATCH v3 0/2] add basic ethdev stats with data object recursion Ciara Power
@ 2020-07-13 14:23 ` Ciara Power
  2020-07-13 14:23   ` [dpdk-dev] [PATCH v4 1/2] telemetry: support array values in data objects Ciara Power
  2020-07-13 14:23   ` [dpdk-dev] [PATCH v4 2/2] ethdev: add common stats for telemetry Ciara Power
  2020-07-15 12:29 ` [dpdk-dev] [PATCH v5 0/3] add basic ethdev stats with data object recursion Ciara Power
                   ` (4 subsequent siblings)
  9 siblings, 2 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-13 14:23 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Ciara Power

v4: Added missing param description to Doxygen comment.
v3:
  - Modified commit logs.
  - Changed function names to reference container.
  - Modified Doxygen comments to reference container.
v2:
  - Added support for arrays to have container values.
  - Added support for int and string arrays within dict/array.
  - Added APIs for internal container memory management.

This patchset adds support for basic ethdev statistics in Telemetry.
To do this, recursive data object support is needed to report the queue
statistics in a list. With this patch, an array or dictionary supports
uint64_t, int or string array type values, which is used for the ethdev
queue stats.

Ciara Power (2):
  telemetry: support array values in data objects
  ethdev: add common stats for telemetry

 lib/librte_ethdev/rte_ethdev.c                | 53 ++++++++++++++
 lib/librte_telemetry/rte_telemetry.h          | 70 +++++++++++++++++++
 .../rte_telemetry_version.map                 |  4 ++
 lib/librte_telemetry/telemetry.c              | 56 +++++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 51 ++++++++++++++
 lib/librte_telemetry/telemetry_data.h         |  7 ++
 lib/librte_telemetry/telemetry_json.h         | 33 +++++++++
 7 files changed, 274 insertions(+)

-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v4 1/2] telemetry: support array values in data objects
  2020-07-13 14:23 ` [dpdk-dev] [PATCH v4 0/2] add basic ethdev stats with data object recursion Ciara Power
@ 2020-07-13 14:23   ` Ciara Power
  2020-07-13 14:23   ` [dpdk-dev] [PATCH v4 2/2] ethdev: add common stats for telemetry Ciara Power
  1 sibling, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-13 14:23 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Ciara Power

Arrays of type uint64_t/int/string can now be included within an array
or dict. One level of embedded containers is supported. This is
necessary to allow for instances such as the ethdev queue stats to be
reported as a list of uint64_t values, rather than having multiple dict
entries with one uint64_t value for each queue stat.

The memory management APIs provided by telemetry simplify the memory
allocation/free aspect of the embedded container. The rte_tel_data_alloc
function is called in the library/app callback to return a pointer to a
container that has been allocated memory. When adding this container
to an array/dict, a parameter is passed to indicate if the memory
should be freed by telemetry after use. This will allow reuse of the
allocated memory if the library/app wishes to do so.

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

---
v4: Added missing param description to Doxygen comment.
v3:
  - Modified commit log.
  - Changed function name to reference container.
  - Modified Doxygen comments to reference container.
v2:
  - Added support for arrays to have container values.
  - Added support for int and string arrays within dict/array.
  - Added APIs for internal container memory management.
---
 lib/librte_telemetry/rte_telemetry.h          | 70 +++++++++++++++++++
 .../rte_telemetry_version.map                 |  4 ++
 lib/librte_telemetry/telemetry.c              | 56 +++++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 51 ++++++++++++++
 lib/librte_telemetry/telemetry_data.h         |  7 ++
 lib/librte_telemetry/telemetry_json.h         | 33 +++++++++
 6 files changed, 221 insertions(+)

diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
index d13010b8f..c16541226 100644
--- a/lib/librte_telemetry/rte_telemetry.h
+++ b/lib/librte_telemetry/rte_telemetry.h
@@ -46,6 +46,7 @@ enum rte_tel_value_type {
 	RTE_TEL_STRING_VAL, /** a string value */
 	RTE_TEL_INT_VAL,    /** a signed 32-bit int value */
 	RTE_TEL_U64_VAL,    /** an unsigned 64-bit int value */
+	RTE_TEL_CONTAINER, /** a container struct */
 };
 
 /**
@@ -136,6 +137,28 @@ __rte_experimental
 int
 rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x);
 
+/**
+ * Add a container to an array. A container is an existing telemetry data
+ * array. The array the container is to be added to must have been started by
+ * rte_tel_data_start_array() with RTE_TEL_CONTAINER as the type parameter.
+ * The container type must be an array of type uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param val
+ *   The pointer to the container to be stored in the array.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep);
+
 /**
  * Add a string value to a dictionary.
  * The dict must have been started by rte_tel_data_start_dict().
@@ -190,6 +213,30 @@ int
 rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 		const char *name, uint64_t val);
 
+/**
+ * Add a container to a dictionary. A container is an existing telemetry data
+ * array. The dict the container is to be added to must have been started by
+ * rte_tel_data_start_dict(). The container must be an array of type
+ * uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param name
+ *   The name the value is to be stored under in the dict.
+ * @param val
+ *   The pointer to the container to be stored in the dict.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep);
+
 /**
  * This telemetry callback is used when registering a telemetry command.
  * It handles getting and formatting information to be returned to telemetry
@@ -264,4 +311,27 @@ int
 rte_telemetry_init(const char *runtime_dir, rte_cpuset_t *cpuset,
 		const char **err_str);
 
+/**
+ * Get a pointer to a container with memory allocated. The container is to be
+ * used embedded within an existing telemetry dict/array.
+ *
+ * @return
+ *  Pointer to a container.
+ */
+__rte_experimental
+struct rte_tel_data *
+rte_tel_data_alloc(void);
+
+/**
+ * @internal
+ * Free a container that has memory allocated.
+ *
+ * @param data
+ *  Pointer to container.
+ *.
+ */
+__rte_experimental
+void
+rte_tel_data_free(struct rte_tel_data *data);
+
 #endif
diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map
index 86433c21d..d1dbf8d58 100644
--- a/lib/librte_telemetry/rte_telemetry_version.map
+++ b/lib/librte_telemetry/rte_telemetry_version.map
@@ -1,12 +1,16 @@
 EXPERIMENTAL {
 	global:
 
+	rte_tel_data_add_array_container;
 	rte_tel_data_add_array_int;
 	rte_tel_data_add_array_string;
 	rte_tel_data_add_array_u64;
+	rte_tel_data_add_dict_container;
 	rte_tel_data_add_dict_int;
 	rte_tel_data_add_dict_string;
 	rte_tel_data_add_dict_u64;
+	rte_tel_data_alloc;
+	rte_tel_data_free;
 	rte_tel_data_start_array;
 	rte_tel_data_start_dict;
 	rte_tel_data_string;
diff --git a/lib/librte_telemetry/telemetry.c b/lib/librte_telemetry/telemetry.c
index e7e3d861d..691b52144 100644
--- a/lib/librte_telemetry/telemetry.c
+++ b/lib/librte_telemetry/telemetry.c
@@ -120,6 +120,35 @@ command_help(const char *cmd __rte_unused, const char *params,
 	return 0;
 }
 
+static int
+container_to_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len)
+{
+	size_t used = 0;
+	unsigned int i;
+
+	if (d->type != RTE_TEL_ARRAY_U64 && d->type != RTE_TEL_ARRAY_INT
+			&& d->type != RTE_TEL_ARRAY_STRING)
+		return snprintf(out_buf, buf_len, "null");
+
+	used = rte_tel_json_empty_array(out_buf, buf_len, 0);
+	if (d->type == RTE_TEL_ARRAY_U64)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_u64(out_buf,
+				buf_len, used,
+				d->data.array[i].u64val);
+	if (d->type == RTE_TEL_ARRAY_INT)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_int(out_buf,
+				buf_len, used,
+				d->data.array[i].ival);
+	if (d->type == RTE_TEL_ARRAY_STRING)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_string(out_buf,
+				buf_len, used,
+				d->data.array[i].sval);
+	return used;
+}
+
 static void
 output_json(const char *cmd, const struct rte_tel_data *d, int s)
 {
@@ -166,6 +195,20 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 						buf_len, used,
 						v->name, v->value.u64val);
 				break;
+			case RTE_TEL_CONTAINER:
+			{
+				char temp[buf_len];
+				const struct container *cont =
+						&v->value.container;
+				if (container_to_json(cont->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_obj_json(
+							cb_data_buf,
+							buf_len, used,
+							v->name, temp);
+				if (!cont->keep)
+					rte_tel_data_free(cont->data);
+			}
 			}
 		}
 		used += prefix_used;
@@ -174,6 +217,7 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 	case RTE_TEL_ARRAY_STRING:
 	case RTE_TEL_ARRAY_INT:
 	case RTE_TEL_ARRAY_U64:
+	case RTE_TEL_ARRAY_CONTAINER:
 		prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
 				MAX_CMD_LEN, cmd);
 		cb_data_buf = &out_buf[prefix_used];
@@ -194,6 +238,18 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 				used = rte_tel_json_add_array_u64(cb_data_buf,
 						buf_len, used,
 						d->data.array[i].u64val);
+			else if (d->type == RTE_TEL_ARRAY_CONTAINER) {
+				char temp[buf_len];
+				const struct container *rec_data =
+						&d->data.array[i].container;
+				if (container_to_json(rec_data->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_array_json(
+							cb_data_buf,
+							buf_len, used, temp);
+				if (!rec_data->keep)
+					rte_tel_data_free(rec_data->data);
+			}
 		used += prefix_used;
 		used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
 		break;
diff --git a/lib/librte_telemetry/telemetry_data.c b/lib/librte_telemetry/telemetry_data.c
index f424bbd48..77b0fe09a 100644
--- a/lib/librte_telemetry/telemetry_data.c
+++ b/lib/librte_telemetry/telemetry_data.c
@@ -14,6 +14,7 @@ rte_tel_data_start_array(struct rte_tel_data *d, enum rte_tel_value_type type)
 			RTE_TEL_ARRAY_STRING, /* RTE_TEL_STRING_VAL = 0 */
 			RTE_TEL_ARRAY_INT,    /* RTE_TEL_INT_VAL = 1 */
 			RTE_TEL_ARRAY_U64,    /* RTE_TEL_u64_VAL = 2 */
+			RTE_TEL_ARRAY_CONTAINER, /* RTE_TEL_CONTAINER = 3 */
 	};
 	d->type = array_types[type];
 	d->data_len = 0;
@@ -74,6 +75,23 @@ rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x)
 	return 0;
 }
 
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep)
+{
+	if (d->type != RTE_TEL_ARRAY_CONTAINER ||
+			(val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_ARRAY_ENTRIES)
+		return -ENOSPC;
+
+	d->data.array[d->data_len].container.data = val;
+	d->data.array[d->data_len++].container.keep = !!keep;
+	return 0;
+}
+
 int
 rte_tel_data_add_dict_string(struct rte_tel_data *d, const char *name,
 		const char *val)
@@ -128,3 +146,36 @@ rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
 	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
 }
+
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep)
+{
+	struct tel_dict_entry *e = &d->data.dict[d->data_len];
+
+	if (d->type != RTE_TEL_DICT || (val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES)
+		return -ENOSPC;
+
+	d->data_len++;
+	e->type = RTE_TEL_CONTAINER;
+	e->value.container.data = val;
+	e->value.container.keep = !!keep;
+	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
+	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
+}
+
+struct rte_tel_data *
+rte_tel_data_alloc(void)
+{
+	return malloc(sizeof(struct rte_tel_data));
+}
+
+void
+rte_tel_data_free(struct rte_tel_data *data)
+{
+	free(data);
+}
diff --git a/lib/librte_telemetry/telemetry_data.h b/lib/librte_telemetry/telemetry_data.h
index ff3a371a3..adb84a09f 100644
--- a/lib/librte_telemetry/telemetry_data.h
+++ b/lib/librte_telemetry/telemetry_data.h
@@ -15,6 +15,12 @@ enum tel_container_types {
 	RTE_TEL_ARRAY_STRING, /** array of string values only */
 	RTE_TEL_ARRAY_INT,    /** array of signed, 32-bit int values */
 	RTE_TEL_ARRAY_U64,    /** array of unsigned 64-bit int values */
+	RTE_TEL_ARRAY_CONTAINER, /** array of container structs */
+};
+
+struct container {
+	struct rte_tel_data *data;
+	int keep;
 };
 
 /* each type here must have an equivalent enum in the value types enum in
@@ -25,6 +31,7 @@ union tel_value {
 	char sval[RTE_TEL_MAX_STRING_LEN];
 	int ival;
 	uint64_t u64val;
+	struct container container;
 };
 
 struct tel_dict_entry {
diff --git a/lib/librte_telemetry/telemetry_json.h b/lib/librte_telemetry/telemetry_json.h
index a2ce4899e..ad270b9b3 100644
--- a/lib/librte_telemetry/telemetry_json.h
+++ b/lib/librte_telemetry/telemetry_json.h
@@ -102,6 +102,22 @@ rte_tel_json_add_array_u64(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/*
+ * Add a new element with raw JSON value to the JSON array stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_array_json(char *buf, const int len, const int used,
+		const char *str)
+{
+	int ret, end = used - 1; /* strip off final delimiter */
+	if (used <= 2) /* assume empty, since minimum is '[]' */
+		return __json_snprintf(buf, len, "[%s]", str);
+
+	ret = __json_snprintf(buf + end, len - end, ",%s]", str);
+	return ret == 0 ? used : end + ret;
+}
+
 /**
  * Add a new element with uint64_t value to the JSON object stored in the
  * provided buffer.
@@ -155,4 +171,21 @@ rte_tel_json_add_obj_str(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/**
+ * Add a new element with raw JSON value to the JSON object stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_obj_json(char *buf, const int len, const int used,
+		const char *name, const char *val)
+{
+	int ret, end = used - 1;
+	if (used <= 2) /* assume empty, since minimum is '{}' */
+		return __json_snprintf(buf, len, "{\"%s\":%s}", name, val);
+
+	ret = __json_snprintf(buf + end, len - end, ",\"%s\":%s}",
+			name, val);
+	return ret == 0 ? used : end + ret;
+}
+
 #endif /*_RTE_TELEMETRY_JSON_H_*/
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v4 2/2] ethdev: add common stats for telemetry
  2020-07-13 14:23 ` [dpdk-dev] [PATCH v4 0/2] add basic ethdev stats with data object recursion Ciara Power
  2020-07-13 14:23   ` [dpdk-dev] [PATCH v4 1/2] telemetry: support array values in data objects Ciara Power
@ 2020-07-13 14:23   ` Ciara Power
  1 sibling, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-13 14:23 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Ciara Power

The ethdev library now registers a telemetry command for common ethdev
statistics.

An example usage is shown below:

Connecting to /var/run/dpdk/rte/dpdk_telemetry.v2
{"version": "DPDK 20.08.0-rc1", "pid": 14119, "max_output_len": 16384}
--> /ethdev/stats,0
{"/ethdev/stats": {"ipackets": 0, "opackets": 0, "ibytes": 0, "obytes": \
    0, "imissed": 0, "ierrors": 0, "oerrors": 0, "rx_nombuf": 0, \
    "q_ipackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_opackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_ibytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_obytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_errors": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}}

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

---
v3:
  - Updated help text and commit subject line.
v2:
  - Updated to use memory management APIs.
---
 lib/librte_ethdev/rte_ethdev.c | 53 ++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
index 7858ad5f1..8ee14b1b5 100644
--- a/lib/librte_ethdev/rte_ethdev.c
+++ b/lib/librte_ethdev/rte_ethdev.c
@@ -5275,6 +5275,57 @@ handle_port_list(const char *cmd __rte_unused,
 	return 0;
 }
 
+static void
+add_port_queue_stats(struct rte_tel_data *d, uint64_t *q_stats,
+		const char *stat_name)
+{
+	int q;
+	struct rte_tel_data *q_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(q_data, RTE_TEL_U64_VAL);
+	for (q = 0; q < RTE_ETHDEV_QUEUE_STAT_CNTRS; q++)
+		rte_tel_data_add_array_u64(q_data, q_stats[q]);
+	rte_tel_data_add_dict_container(d, stat_name, q_data, 0);
+}
+
+#define ADD_DICT_STAT(stats, s) rte_tel_data_add_dict_u64(d, #s, stats.s)
+
+static int
+handle_port_stats(const char *cmd __rte_unused,
+		const char *params,
+		struct rte_tel_data *d)
+{
+	struct rte_eth_stats stats;
+	int port_id, ret;
+
+	if (params == NULL || strlen(params) == 0 || !isdigit(*params))
+		return -1;
+
+	port_id = atoi(params);
+	if (!rte_eth_dev_is_valid_port(port_id))
+		return -1;
+
+	ret = rte_eth_stats_get(port_id, &stats);
+	if (ret < 0)
+		return -1;
+
+	rte_tel_data_start_dict(d);
+	ADD_DICT_STAT(stats, ipackets);
+	ADD_DICT_STAT(stats, opackets);
+	ADD_DICT_STAT(stats, ibytes);
+	ADD_DICT_STAT(stats, obytes);
+	ADD_DICT_STAT(stats, imissed);
+	ADD_DICT_STAT(stats, ierrors);
+	ADD_DICT_STAT(stats, oerrors);
+	ADD_DICT_STAT(stats, rx_nombuf);
+	add_port_queue_stats(d, stats.q_ipackets, "q_ipackets");
+	add_port_queue_stats(d, stats.q_opackets, "q_opackets");
+	add_port_queue_stats(d, stats.q_ibytes, "q_ibytes");
+	add_port_queue_stats(d, stats.q_obytes, "q_obytes");
+	add_port_queue_stats(d, stats.q_errors, "q_errors");
+
+	return 0;
+}
+
 static int
 handle_port_xstats(const char *cmd __rte_unused,
 		const char *params,
@@ -5361,6 +5412,8 @@ RTE_INIT(ethdev_init_telemetry)
 {
 	rte_telemetry_register_cmd("/ethdev/list", handle_port_list,
 			"Returns list of available ethdev ports. Takes no parameters");
+	rte_telemetry_register_cmd("/ethdev/stats", handle_port_stats,
+			"Returns the common stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/xstats", handle_port_xstats,
 			"Returns the extended stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/link_status",
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v5 0/3] add basic ethdev stats with data object recursion
  2020-06-12 10:53 [dpdk-dev] [RFC 0/2] add basic ethdev stats with data object recursion Ciara Power
                   ` (4 preceding siblings ...)
  2020-07-13 14:23 ` [dpdk-dev] [PATCH v4 0/2] add basic ethdev stats with data object recursion Ciara Power
@ 2020-07-15 12:29 ` Ciara Power
  2020-07-15 12:29   ` [dpdk-dev] [PATCH v5 1/3] telemetry: support array values in data objects Ciara Power
                     ` (2 more replies)
  2020-07-20 11:19 ` [dpdk-dev] [PATCH v6 0/3] add basic ethdev stats with data object recursion Ciara Power
                   ` (3 subsequent siblings)
  9 siblings, 3 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-15 12:29 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Ciara Power

v5: Added unit tests for telemetry data to JSON conversion.
v4: Added missing param description to Doxygen comment.
v3:
  - Modified commit logs.
  - Changed function names to reference container.
  - Modified Doxygen comments to reference container.
v2:
  - Added support for arrays to have container values.
  - Added support for int and string arrays within dict/array.
  - Added APIs for internal container memory management.

This patchset adds support for basic ethdev statistics in Telemetry.
To do this, recursive data object support is needed to report the queue
statistics in a list. With this patch, an array or dictionary supports
uint64_t, int or string array type values, which is used for the ethdev
queue stats.

Ciara Power (2):
  telemetry: support array values in data objects
  ethdev: add common stats for telemetry

Louise Kilheeney (1):
  test/test_telemetry_data: add unit tests for data to JSON

 app/test/Makefile                             |   1 +
 app/test/meson.build                          |   5 +-
 app/test/test_telemetry_data.c                | 359 ++++++++++++++++++
 lib/librte_ethdev/rte_ethdev.c                |  53 +++
 lib/librte_telemetry/rte_telemetry.h          |  70 ++++
 .../rte_telemetry_version.map                 |   4 +
 lib/librte_telemetry/telemetry.c              |  56 +++
 lib/librte_telemetry/telemetry_data.c         |  51 +++
 lib/librte_telemetry/telemetry_data.h         |   7 +
 lib/librte_telemetry/telemetry_json.h         |  33 ++
 10 files changed, 637 insertions(+), 2 deletions(-)
 create mode 100644 app/test/test_telemetry_data.c

-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v5 1/3] telemetry: support array values in data objects
  2020-07-15 12:29 ` [dpdk-dev] [PATCH v5 0/3] add basic ethdev stats with data object recursion Ciara Power
@ 2020-07-15 12:29   ` Ciara Power
  2020-07-15 12:29   ` [dpdk-dev] [PATCH v5 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
  2020-07-15 12:29   ` [dpdk-dev] [PATCH v5 3/3] ethdev: add common stats for telemetry Ciara Power
  2 siblings, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-15 12:29 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Ciara Power

Arrays of type uint64_t/int/string can now be included within an array
or dict. One level of embedded containers is supported. This is
necessary to allow for instances such as the ethdev queue stats to be
reported as a list of uint64_t values, rather than having multiple dict
entries with one uint64_t value for each queue stat.

The memory management APIs provided by telemetry simplify the memory
allocation/free aspect of the embedded container. The rte_tel_data_alloc
function is called in the library/app callback to return a pointer to a
container that has been allocated memory. When adding this container
to an array/dict, a parameter is passed to indicate if the memory
should be freed by telemetry after use. This will allow reuse of the
allocated memory if the library/app wishes to do so.

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

---
v4: Added missing param description to Doxygen comment.
v3:
  - Modified commit log.
  - Changed function name to reference container.
  - Modified Doxygen comments to reference container.
v2:
  - Added support for arrays to have container values.
  - Added support for int and string arrays within dict/array.
  - Added APIs for internal container memory management.
---
 lib/librte_telemetry/rte_telemetry.h          | 70 +++++++++++++++++++
 .../rte_telemetry_version.map                 |  4 ++
 lib/librte_telemetry/telemetry.c              | 56 +++++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 51 ++++++++++++++
 lib/librte_telemetry/telemetry_data.h         |  7 ++
 lib/librte_telemetry/telemetry_json.h         | 33 +++++++++
 6 files changed, 221 insertions(+)

diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
index d13010b8f..c16541226 100644
--- a/lib/librte_telemetry/rte_telemetry.h
+++ b/lib/librte_telemetry/rte_telemetry.h
@@ -46,6 +46,7 @@ enum rte_tel_value_type {
 	RTE_TEL_STRING_VAL, /** a string value */
 	RTE_TEL_INT_VAL,    /** a signed 32-bit int value */
 	RTE_TEL_U64_VAL,    /** an unsigned 64-bit int value */
+	RTE_TEL_CONTAINER, /** a container struct */
 };
 
 /**
@@ -136,6 +137,28 @@ __rte_experimental
 int
 rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x);
 
+/**
+ * Add a container to an array. A container is an existing telemetry data
+ * array. The array the container is to be added to must have been started by
+ * rte_tel_data_start_array() with RTE_TEL_CONTAINER as the type parameter.
+ * The container type must be an array of type uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param val
+ *   The pointer to the container to be stored in the array.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep);
+
 /**
  * Add a string value to a dictionary.
  * The dict must have been started by rte_tel_data_start_dict().
@@ -190,6 +213,30 @@ int
 rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 		const char *name, uint64_t val);
 
+/**
+ * Add a container to a dictionary. A container is an existing telemetry data
+ * array. The dict the container is to be added to must have been started by
+ * rte_tel_data_start_dict(). The container must be an array of type
+ * uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param name
+ *   The name the value is to be stored under in the dict.
+ * @param val
+ *   The pointer to the container to be stored in the dict.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep);
+
 /**
  * This telemetry callback is used when registering a telemetry command.
  * It handles getting and formatting information to be returned to telemetry
@@ -264,4 +311,27 @@ int
 rte_telemetry_init(const char *runtime_dir, rte_cpuset_t *cpuset,
 		const char **err_str);
 
+/**
+ * Get a pointer to a container with memory allocated. The container is to be
+ * used embedded within an existing telemetry dict/array.
+ *
+ * @return
+ *  Pointer to a container.
+ */
+__rte_experimental
+struct rte_tel_data *
+rte_tel_data_alloc(void);
+
+/**
+ * @internal
+ * Free a container that has memory allocated.
+ *
+ * @param data
+ *  Pointer to container.
+ *.
+ */
+__rte_experimental
+void
+rte_tel_data_free(struct rte_tel_data *data);
+
 #endif
diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map
index 86433c21d..d1dbf8d58 100644
--- a/lib/librte_telemetry/rte_telemetry_version.map
+++ b/lib/librte_telemetry/rte_telemetry_version.map
@@ -1,12 +1,16 @@
 EXPERIMENTAL {
 	global:
 
+	rte_tel_data_add_array_container;
 	rte_tel_data_add_array_int;
 	rte_tel_data_add_array_string;
 	rte_tel_data_add_array_u64;
+	rte_tel_data_add_dict_container;
 	rte_tel_data_add_dict_int;
 	rte_tel_data_add_dict_string;
 	rte_tel_data_add_dict_u64;
+	rte_tel_data_alloc;
+	rte_tel_data_free;
 	rte_tel_data_start_array;
 	rte_tel_data_start_dict;
 	rte_tel_data_string;
diff --git a/lib/librte_telemetry/telemetry.c b/lib/librte_telemetry/telemetry.c
index e7e3d861d..691b52144 100644
--- a/lib/librte_telemetry/telemetry.c
+++ b/lib/librte_telemetry/telemetry.c
@@ -120,6 +120,35 @@ command_help(const char *cmd __rte_unused, const char *params,
 	return 0;
 }
 
+static int
+container_to_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len)
+{
+	size_t used = 0;
+	unsigned int i;
+
+	if (d->type != RTE_TEL_ARRAY_U64 && d->type != RTE_TEL_ARRAY_INT
+			&& d->type != RTE_TEL_ARRAY_STRING)
+		return snprintf(out_buf, buf_len, "null");
+
+	used = rte_tel_json_empty_array(out_buf, buf_len, 0);
+	if (d->type == RTE_TEL_ARRAY_U64)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_u64(out_buf,
+				buf_len, used,
+				d->data.array[i].u64val);
+	if (d->type == RTE_TEL_ARRAY_INT)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_int(out_buf,
+				buf_len, used,
+				d->data.array[i].ival);
+	if (d->type == RTE_TEL_ARRAY_STRING)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_string(out_buf,
+				buf_len, used,
+				d->data.array[i].sval);
+	return used;
+}
+
 static void
 output_json(const char *cmd, const struct rte_tel_data *d, int s)
 {
@@ -166,6 +195,20 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 						buf_len, used,
 						v->name, v->value.u64val);
 				break;
+			case RTE_TEL_CONTAINER:
+			{
+				char temp[buf_len];
+				const struct container *cont =
+						&v->value.container;
+				if (container_to_json(cont->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_obj_json(
+							cb_data_buf,
+							buf_len, used,
+							v->name, temp);
+				if (!cont->keep)
+					rte_tel_data_free(cont->data);
+			}
 			}
 		}
 		used += prefix_used;
@@ -174,6 +217,7 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 	case RTE_TEL_ARRAY_STRING:
 	case RTE_TEL_ARRAY_INT:
 	case RTE_TEL_ARRAY_U64:
+	case RTE_TEL_ARRAY_CONTAINER:
 		prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
 				MAX_CMD_LEN, cmd);
 		cb_data_buf = &out_buf[prefix_used];
@@ -194,6 +238,18 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 				used = rte_tel_json_add_array_u64(cb_data_buf,
 						buf_len, used,
 						d->data.array[i].u64val);
+			else if (d->type == RTE_TEL_ARRAY_CONTAINER) {
+				char temp[buf_len];
+				const struct container *rec_data =
+						&d->data.array[i].container;
+				if (container_to_json(rec_data->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_array_json(
+							cb_data_buf,
+							buf_len, used, temp);
+				if (!rec_data->keep)
+					rte_tel_data_free(rec_data->data);
+			}
 		used += prefix_used;
 		used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
 		break;
diff --git a/lib/librte_telemetry/telemetry_data.c b/lib/librte_telemetry/telemetry_data.c
index f424bbd48..77b0fe09a 100644
--- a/lib/librte_telemetry/telemetry_data.c
+++ b/lib/librte_telemetry/telemetry_data.c
@@ -14,6 +14,7 @@ rte_tel_data_start_array(struct rte_tel_data *d, enum rte_tel_value_type type)
 			RTE_TEL_ARRAY_STRING, /* RTE_TEL_STRING_VAL = 0 */
 			RTE_TEL_ARRAY_INT,    /* RTE_TEL_INT_VAL = 1 */
 			RTE_TEL_ARRAY_U64,    /* RTE_TEL_u64_VAL = 2 */
+			RTE_TEL_ARRAY_CONTAINER, /* RTE_TEL_CONTAINER = 3 */
 	};
 	d->type = array_types[type];
 	d->data_len = 0;
@@ -74,6 +75,23 @@ rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x)
 	return 0;
 }
 
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep)
+{
+	if (d->type != RTE_TEL_ARRAY_CONTAINER ||
+			(val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_ARRAY_ENTRIES)
+		return -ENOSPC;
+
+	d->data.array[d->data_len].container.data = val;
+	d->data.array[d->data_len++].container.keep = !!keep;
+	return 0;
+}
+
 int
 rte_tel_data_add_dict_string(struct rte_tel_data *d, const char *name,
 		const char *val)
@@ -128,3 +146,36 @@ rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
 	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
 }
+
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep)
+{
+	struct tel_dict_entry *e = &d->data.dict[d->data_len];
+
+	if (d->type != RTE_TEL_DICT || (val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES)
+		return -ENOSPC;
+
+	d->data_len++;
+	e->type = RTE_TEL_CONTAINER;
+	e->value.container.data = val;
+	e->value.container.keep = !!keep;
+	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
+	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
+}
+
+struct rte_tel_data *
+rte_tel_data_alloc(void)
+{
+	return malloc(sizeof(struct rte_tel_data));
+}
+
+void
+rte_tel_data_free(struct rte_tel_data *data)
+{
+	free(data);
+}
diff --git a/lib/librte_telemetry/telemetry_data.h b/lib/librte_telemetry/telemetry_data.h
index ff3a371a3..adb84a09f 100644
--- a/lib/librte_telemetry/telemetry_data.h
+++ b/lib/librte_telemetry/telemetry_data.h
@@ -15,6 +15,12 @@ enum tel_container_types {
 	RTE_TEL_ARRAY_STRING, /** array of string values only */
 	RTE_TEL_ARRAY_INT,    /** array of signed, 32-bit int values */
 	RTE_TEL_ARRAY_U64,    /** array of unsigned 64-bit int values */
+	RTE_TEL_ARRAY_CONTAINER, /** array of container structs */
+};
+
+struct container {
+	struct rte_tel_data *data;
+	int keep;
 };
 
 /* each type here must have an equivalent enum in the value types enum in
@@ -25,6 +31,7 @@ union tel_value {
 	char sval[RTE_TEL_MAX_STRING_LEN];
 	int ival;
 	uint64_t u64val;
+	struct container container;
 };
 
 struct tel_dict_entry {
diff --git a/lib/librte_telemetry/telemetry_json.h b/lib/librte_telemetry/telemetry_json.h
index a2ce4899e..ad270b9b3 100644
--- a/lib/librte_telemetry/telemetry_json.h
+++ b/lib/librte_telemetry/telemetry_json.h
@@ -102,6 +102,22 @@ rte_tel_json_add_array_u64(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/*
+ * Add a new element with raw JSON value to the JSON array stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_array_json(char *buf, const int len, const int used,
+		const char *str)
+{
+	int ret, end = used - 1; /* strip off final delimiter */
+	if (used <= 2) /* assume empty, since minimum is '[]' */
+		return __json_snprintf(buf, len, "[%s]", str);
+
+	ret = __json_snprintf(buf + end, len - end, ",%s]", str);
+	return ret == 0 ? used : end + ret;
+}
+
 /**
  * Add a new element with uint64_t value to the JSON object stored in the
  * provided buffer.
@@ -155,4 +171,21 @@ rte_tel_json_add_obj_str(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/**
+ * Add a new element with raw JSON value to the JSON object stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_obj_json(char *buf, const int len, const int used,
+		const char *name, const char *val)
+{
+	int ret, end = used - 1;
+	if (used <= 2) /* assume empty, since minimum is '{}' */
+		return __json_snprintf(buf, len, "{\"%s\":%s}", name, val);
+
+	ret = __json_snprintf(buf + end, len - end, ",\"%s\":%s}",
+			name, val);
+	return ret == 0 ? used : end + ret;
+}
+
 #endif /*_RTE_TELEMETRY_JSON_H_*/
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v5 2/3] test/test_telemetry_data: add unit tests for data to JSON
  2020-07-15 12:29 ` [dpdk-dev] [PATCH v5 0/3] add basic ethdev stats with data object recursion Ciara Power
  2020-07-15 12:29   ` [dpdk-dev] [PATCH v5 1/3] telemetry: support array values in data objects Ciara Power
@ 2020-07-15 12:29   ` Ciara Power
  2020-07-17 11:53     ` Bruce Richardson
  2020-07-15 12:29   ` [dpdk-dev] [PATCH v5 3/3] ethdev: add common stats for telemetry Ciara Power
  2 siblings, 1 reply; 44+ messages in thread
From: Ciara Power @ 2020-07-15 12:29 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Louise Kilheeney, Ciara Power

From: Louise Kilheeney <louise.kilheeney@intel.com>

This patch adds tests for verifying telemetry data structures are
converted to JSON as expected. Both flat and recursive data structures
are tested, for all possible value types.

Signed-off-by: Louise Kilheeney <louise.kilheeney@intel.com>
Signed-off-by: Ciara Power <ciara.power@intel.com>
---
 app/test/Makefile              |   1 +
 app/test/meson.build           |   5 +-
 app/test/test_telemetry_data.c | 359 +++++++++++++++++++++++++++++++++
 3 files changed, 363 insertions(+), 2 deletions(-)
 create mode 100644 app/test/test_telemetry_data.c

diff --git a/app/test/Makefile b/app/test/Makefile
index f4065271e..1cb64089c 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -145,6 +145,7 @@ SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6.c
 SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6_perf.c
 
 SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += test_telemetry_json.c
+SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += test_telemetry_data.c
 
 SRCS-y += test_debug.c
 SRCS-y += test_errno.c
diff --git a/app/test/meson.build b/app/test/meson.build
index 786a21397..4a72fe5b6 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -170,6 +170,7 @@ test_deps = ['acl',
 	'ring',
 	'security',
 	'stack',
+	'telemetry',
 	'timer'
 ]
 
@@ -345,8 +346,8 @@ if dpdk_conf.has('RTE_LIBRTE_SKELETON_EVENTDEV_PMD')
 	test_deps += 'pmd_skeleton_event'
 endif
 if dpdk_conf.has('RTE_LIBRTE_TELEMETRY')
-	test_sources += 'test_telemetry_json.c'
-	fast_tests += [['telemetry_json_autotest', true]]
+	test_sources += ['test_telemetry_json.c', 'test_telemetry_data.c']
+	fast_tests += [['telemetry_json_autotest', true], ['telemetry_data_autotest', true]]
 endif
 
 # The following linkages of drivers are required because
diff --git a/app/test/test_telemetry_data.c b/app/test/test_telemetry_data.c
new file mode 100644
index 000000000..670af9134
--- /dev/null
+++ b/app/test/test_telemetry_data.c
@@ -0,0 +1,359 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2020 Intel Corporation
+ */
+
+#include <glob.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <rte_eal.h>
+#include <rte_common.h>
+#include <rte_telemetry.h>
+#include <rte_string_fns.h>
+
+#include "test.h"
+#include "telemetry_data.h"
+
+#define TELEMETRY_VERSION "v2"
+#define REQUEST_CMD "/test"
+#define BUF_SIZE 1024
+#define TEST_OUTPUT(exp) test_output(__func__, exp)
+
+static struct rte_tel_data response_data;
+static int sock;
+
+static int
+test_cb(const char *cmd __rte_unused, const char *params __rte_unused,
+		struct rte_tel_data *d)
+{
+	*d = response_data;
+	return 0;
+}
+
+static int
+test_output(const char *func_name, const char *expected)
+{
+	int bytes;
+	char buf[BUF_SIZE * 16];
+	if (write(sock, REQUEST_CMD, strlen(REQUEST_CMD)) < 0) {
+		printf("%s: Error with socket write - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	bytes = read(sock, buf, sizeof(buf));
+	if (bytes < 0) {
+		printf("%s: Error with socket read - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	buf[bytes] = '\0';
+	printf("%s: buf = '%s', expected = '%s'\n", func_name, buf, expected);
+	return strncmp(expected, buf, sizeof(buf));
+}
+
+static int
+test_dict_with_array_int_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_INT_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_INT_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_int(child_data, i);
+		rte_tel_data_add_array_int(child_data2, i);
+	}
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[0,1,2,3,4],\"dict_1\":[0,1,2,3,4]}}");
+}
+
+static int
+test_array_with_array_int_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_INT_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_INT_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_int(child_data, i);
+		rte_tel_data_add_array_int(child_data2, i);
+	}
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[0,1,2,3,4],[0,1,2,3,4]]}");
+}
+
+static int
+test_case_array_int(void)
+{
+	int i;
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_INT_VAL);
+	for (i = 0; i < 5; i++)
+		rte_tel_data_add_array_int(&response_data, i);
+	return TEST_OUTPUT("{\"/test\":[0,1,2,3,4]}");
+}
+
+static int
+test_case_add_dict_int(void)
+{
+	int i = 0;
+	char name_of_value[8];
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		sprintf(name_of_value, "dict_%d", i);
+		rte_tel_data_add_dict_int(&response_data, name_of_value, i);
+	}
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":0,\"dict_1\":1,\"dict_2\":2,\"dict_3\":3,\"dict_4\":4}}");
+}
+
+static int
+test_case_array_string(void)
+{
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_STRING_VAL);
+	rte_tel_data_add_array_string(&response_data, "aaaa");
+	rte_tel_data_add_array_string(&response_data, "bbbb");
+	rte_tel_data_add_array_string(&response_data, "cccc");
+	rte_tel_data_add_array_string(&response_data, "dddd");
+	rte_tel_data_add_array_string(&response_data, "eeee");
+
+	return TEST_OUTPUT("{\"/test\":[\"aaaa\",\"bbbb\",\"cccc\",\"dddd\",\"eeee\"]}");
+}
+
+static int
+test_case_add_dict_string(void)
+{
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	rte_tel_data_add_dict_string(&response_data, "dict_0", "aaaa");
+	rte_tel_data_add_dict_string(&response_data, "dict_1", "bbbb");
+	rte_tel_data_add_dict_string(&response_data, "dict_2", "cccc");
+	rte_tel_data_add_dict_string(&response_data, "dict_3", "dddd");
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":\"aaaa\",\"dict_1\":\"bbbb\",\"dict_2\":\"cccc\",\"dict_3\":\"dddd\"}}");
+}
+
+
+static int
+test_dict_with_array_string_values(void)
+{
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_STRING_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_STRING_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	rte_tel_data_add_array_string(child_data, "aaaa");
+	rte_tel_data_add_array_string(child_data2, "bbbb");
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[\"aaaa\"],\"dict_1\":[\"bbbb\"]}}");
+}
+
+static int
+test_array_with_array_string_values(void)
+{
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_STRING_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_STRING_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	rte_tel_data_add_array_string(child_data, "aaaa");
+	rte_tel_data_add_array_string(child_data2, "bbbb");
+
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[\"aaaa\"],[\"bbbb\"]]}");
+}
+
+static int
+test_case_array_u64(void)
+{
+	int i;
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_U64_VAL);
+	for (i = 0; i < 5; i++)
+		rte_tel_data_add_array_u64(&response_data, i);
+	return TEST_OUTPUT("{\"/test\":[0,1,2,3,4]}");
+}
+
+static int
+test_case_add_dict_u64(void)
+{
+	int i = 0;
+	char name_of_value[8];
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		sprintf(name_of_value, "dict_%d", i);
+		rte_tel_data_add_dict_u64(&response_data, name_of_value, i);
+	}
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":0,\"dict_1\":1,\"dict_2\":2,\"dict_3\":3,\"dict_4\":4}}");
+}
+
+static int
+test_dict_with_array_u64_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_U64_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_U64_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 10; i++) {
+		rte_tel_data_add_array_u64(child_data, i);
+		rte_tel_data_add_array_u64(child_data2, i);
+	}
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[0,1,2,3,4,5,6,7,8,9],\"dict_1\":[0,1,2,3,4,5,6,7,8,9]}}");
+}
+
+static int
+test_array_with_array_u64_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_U64_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_U64_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_u64(child_data, i);
+		rte_tel_data_add_array_u64(child_data2, i);
+	}
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[0,1,2,3,4],[0,1,2,3,4]]}");
+}
+
+static int
+connect_to_socket(void)
+{
+	char file_pattern[PATH_MAX];
+	char buf[BUF_SIZE];
+	glob_t globbuf;
+	int sock, bytes;
+	struct sockaddr_un telem_addr;
+
+	snprintf(file_pattern, sizeof(file_pattern),
+			"%s/dpdk_telemetry.%s",	rte_eal_get_runtime_dir(),
+			TELEMETRY_VERSION);
+	if (glob(file_pattern, 0, NULL, &globbuf) != 0) {
+		printf("\n%s: Error finding socket file path: %s\n", __func__,
+				strerror(errno));
+		globfree(&globbuf);
+		return -1;
+	}
+
+	sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+	if (sock < 0) {
+		printf("\n%s: Error creating socket: %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	telem_addr.sun_family = AF_UNIX;
+	strlcpy(telem_addr.sun_path, globbuf.gl_pathv[0],
+			sizeof(telem_addr.sun_path));
+	if (connect(sock, &telem_addr, sizeof(telem_addr)) < 0)
+		printf("\n%s: Error connecting to socket: %s\n", __func__,
+				strerror(errno));
+
+	bytes = read(sock, buf, sizeof(buf));
+	if (bytes < 0) {
+		printf("%s: Error with socket read - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	buf[bytes] = '\0';
+	printf("\n%s: %s\n", __func__, buf);
+	globfree(&globbuf);
+	return sock;
+}
+
+static int
+test_telemetry_data(void)
+{
+	sock = connect_to_socket();
+	if (sock <= 0)
+		return -1;
+
+	rte_telemetry_register_cmd(REQUEST_CMD, test_cb, "Test");
+	if (test_case_array_string() != 0
+	|| test_case_array_int() != 0
+	|| test_case_array_u64() != 0
+	|| test_case_add_dict_int() != 0
+	|| test_case_add_dict_u64() != 0
+	|| test_case_add_dict_string() != 0
+	||  test_dict_with_array_int_values() != 0
+	|| test_dict_with_array_u64_values() != 0
+	|| test_dict_with_array_string_values() != 0
+	|| test_array_with_array_int_values() != 0
+	|| test_array_with_array_u64_values() != 0
+	|| test_array_with_array_string_values() != 0) {
+		close(sock);
+		return -1;
+	}
+
+	close(sock);
+	return 0;
+}
+
+REGISTER_TEST_COMMAND(telemetry_data_autotest, test_telemetry_data);
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v5 3/3] ethdev: add common stats for telemetry
  2020-07-15 12:29 ` [dpdk-dev] [PATCH v5 0/3] add basic ethdev stats with data object recursion Ciara Power
  2020-07-15 12:29   ` [dpdk-dev] [PATCH v5 1/3] telemetry: support array values in data objects Ciara Power
  2020-07-15 12:29   ` [dpdk-dev] [PATCH v5 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
@ 2020-07-15 12:29   ` Ciara Power
  2 siblings, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-15 12:29 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, Ciara Power

The ethdev library now registers a telemetry command for common ethdev
statistics.

An example usage is shown below:

Connecting to /var/run/dpdk/rte/dpdk_telemetry.v2
{"version": "DPDK 20.08.0-rc1", "pid": 14119, "max_output_len": 16384}
--> /ethdev/stats,0
{"/ethdev/stats": {"ipackets": 0, "opackets": 0, "ibytes": 0, "obytes": \
    0, "imissed": 0, "ierrors": 0, "oerrors": 0, "rx_nombuf": 0, \
    "q_ipackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_opackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_ibytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_obytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_errors": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}}

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

---
v3:
  - Updated help text and commit subject line.
v2:
  - Updated to use memory management APIs.
---
 lib/librte_ethdev/rte_ethdev.c | 53 ++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
index 7858ad5f1..8ee14b1b5 100644
--- a/lib/librte_ethdev/rte_ethdev.c
+++ b/lib/librte_ethdev/rte_ethdev.c
@@ -5275,6 +5275,57 @@ handle_port_list(const char *cmd __rte_unused,
 	return 0;
 }
 
+static void
+add_port_queue_stats(struct rte_tel_data *d, uint64_t *q_stats,
+		const char *stat_name)
+{
+	int q;
+	struct rte_tel_data *q_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(q_data, RTE_TEL_U64_VAL);
+	for (q = 0; q < RTE_ETHDEV_QUEUE_STAT_CNTRS; q++)
+		rte_tel_data_add_array_u64(q_data, q_stats[q]);
+	rte_tel_data_add_dict_container(d, stat_name, q_data, 0);
+}
+
+#define ADD_DICT_STAT(stats, s) rte_tel_data_add_dict_u64(d, #s, stats.s)
+
+static int
+handle_port_stats(const char *cmd __rte_unused,
+		const char *params,
+		struct rte_tel_data *d)
+{
+	struct rte_eth_stats stats;
+	int port_id, ret;
+
+	if (params == NULL || strlen(params) == 0 || !isdigit(*params))
+		return -1;
+
+	port_id = atoi(params);
+	if (!rte_eth_dev_is_valid_port(port_id))
+		return -1;
+
+	ret = rte_eth_stats_get(port_id, &stats);
+	if (ret < 0)
+		return -1;
+
+	rte_tel_data_start_dict(d);
+	ADD_DICT_STAT(stats, ipackets);
+	ADD_DICT_STAT(stats, opackets);
+	ADD_DICT_STAT(stats, ibytes);
+	ADD_DICT_STAT(stats, obytes);
+	ADD_DICT_STAT(stats, imissed);
+	ADD_DICT_STAT(stats, ierrors);
+	ADD_DICT_STAT(stats, oerrors);
+	ADD_DICT_STAT(stats, rx_nombuf);
+	add_port_queue_stats(d, stats.q_ipackets, "q_ipackets");
+	add_port_queue_stats(d, stats.q_opackets, "q_opackets");
+	add_port_queue_stats(d, stats.q_ibytes, "q_ibytes");
+	add_port_queue_stats(d, stats.q_obytes, "q_obytes");
+	add_port_queue_stats(d, stats.q_errors, "q_errors");
+
+	return 0;
+}
+
 static int
 handle_port_xstats(const char *cmd __rte_unused,
 		const char *params,
@@ -5361,6 +5412,8 @@ RTE_INIT(ethdev_init_telemetry)
 {
 	rte_telemetry_register_cmd("/ethdev/list", handle_port_list,
 			"Returns list of available ethdev ports. Takes no parameters");
+	rte_telemetry_register_cmd("/ethdev/stats", handle_port_stats,
+			"Returns the common stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/xstats", handle_port_xstats,
 			"Returns the extended stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/link_status",
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [dpdk-dev] [PATCH v5 2/3] test/test_telemetry_data: add unit tests for data to JSON
  2020-07-15 12:29   ` [dpdk-dev] [PATCH v5 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
@ 2020-07-17 11:53     ` Bruce Richardson
  0 siblings, 0 replies; 44+ messages in thread
From: Bruce Richardson @ 2020-07-17 11:53 UTC (permalink / raw)
  To: Ciara Power
  Cc: kevin.laatz, thomas, ferruh.yigit, arybchenko, dev, keith.wiles,
	Louise Kilheeney

On Wed, Jul 15, 2020 at 01:29:34PM +0100, Ciara Power wrote:
> From: Louise Kilheeney <louise.kilheeney@intel.com>
> 
> This patch adds tests for verifying telemetry data structures are
> converted to JSON as expected. Both flat and recursive data structures
> are tested, for all possible value types.
> 
> Signed-off-by: Louise Kilheeney <louise.kilheeney@intel.com>
> Signed-off-by: Ciara Power <ciara.power@intel.com>

Great to see some unit tests included. However, I think the documenting of
what is happening is missing in comments and in the commit log. I think you
need to explain in the commit log the general pattern used for the tests,
which seems to be having a different response copied to the user each time
the callback is called, and verifing that the output got is what was
built-up internally. 
This should also be explained via comments in the code too, as I call out
below.

> ---
>  app/test/Makefile              |   1 +
>  app/test/meson.build           |   5 +-
>  app/test/test_telemetry_data.c | 359 +++++++++++++++++++++++++++++++++
>  3 files changed, 363 insertions(+), 2 deletions(-)
>  create mode 100644 app/test/test_telemetry_data.c
> 
> diff --git a/app/test/Makefile b/app/test/Makefile
> index f4065271e..1cb64089c 100644
> --- a/app/test/Makefile
> +++ b/app/test/Makefile
> @@ -145,6 +145,7 @@ SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6.c
>  SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6_perf.c
>  
>  SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += test_telemetry_json.c
> +SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += test_telemetry_data.c
>  
>  SRCS-y += test_debug.c
>  SRCS-y += test_errno.c
> diff --git a/app/test/meson.build b/app/test/meson.build
> index 786a21397..4a72fe5b6 100644
> --- a/app/test/meson.build
> +++ b/app/test/meson.build
> @@ -170,6 +170,7 @@ test_deps = ['acl',
>  	'ring',
>  	'security',
>  	'stack',
> +	'telemetry',
>  	'timer'
>  ]
>  
> @@ -345,8 +346,8 @@ if dpdk_conf.has('RTE_LIBRTE_SKELETON_EVENTDEV_PMD')
>  	test_deps += 'pmd_skeleton_event'
>  endif
>  if dpdk_conf.has('RTE_LIBRTE_TELEMETRY')
> -	test_sources += 'test_telemetry_json.c'
> -	fast_tests += [['telemetry_json_autotest', true]]
> +	test_sources += ['test_telemetry_json.c', 'test_telemetry_data.c']
> +	fast_tests += [['telemetry_json_autotest', true], ['telemetry_data_autotest', true]]
>  endif
>  
>  # The following linkages of drivers are required because
> diff --git a/app/test/test_telemetry_data.c b/app/test/test_telemetry_data.c
> new file mode 100644
> index 000000000..670af9134
> --- /dev/null
> +++ b/app/test/test_telemetry_data.c
> @@ -0,0 +1,359 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright 2020 Intel Corporation
> + */
> +
> +#include <glob.h>
> +#include <string.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
> +#include <unistd.h>
> +#include <limits.h>
> +
> +#include <rte_eal.h>
> +#include <rte_common.h>
> +#include <rte_telemetry.h>
> +#include <rte_string_fns.h>
> +
> +#include "test.h"
> +#include "telemetry_data.h"
> +
> +#define TELEMETRY_VERSION "v2"
> +#define REQUEST_CMD "/test"
> +#define BUF_SIZE 1024
> +#define TEST_OUTPUT(exp) test_output(__func__, exp)
> +
> +static struct rte_tel_data response_data;
> +static int sock;
> +
> +static int
> +test_cb(const char *cmd __rte_unused, const char *params __rte_unused,
> +		struct rte_tel_data *d)
> +{
> +	*d = response_data;
> +	return 0;
> +}

This function needs a comment explaining what it does and why.

> +
> +static int
> +test_output(const char *func_name, const char *expected)
> +{
> +	int bytes;
> +	char buf[BUF_SIZE * 16];
> +	if (write(sock, REQUEST_CMD, strlen(REQUEST_CMD)) < 0) {
> +		printf("%s: Error with socket write - %s\n", __func__,
> +				strerror(errno));
> +		return -1;
> +	}
> +	bytes = read(sock, buf, sizeof(buf));
> +	if (bytes < 0) {
> +		printf("%s: Error with socket read - %s\n", __func__,
> +				strerror(errno));
> +		return -1;
> +	}
> +	buf[bytes] = '\0';
> +	printf("%s: buf = '%s', expected = '%s'\n", func_name, buf, expected);
> +	return strncmp(expected, buf, sizeof(buf));
> +}
> +

This one too, needs comment explaining how it works etc.

<snip>
> +static int
> +test_telemetry_data(void)
> +{
> +	sock = connect_to_socket();
> +	if (sock <= 0)
> +		return -1;
> +
> +	rte_telemetry_register_cmd(REQUEST_CMD, test_cb, "Test");
> +	if (test_case_array_string() != 0
> +	|| test_case_array_int() != 0
> +	|| test_case_array_u64() != 0
> +	|| test_case_add_dict_int() != 0
> +	|| test_case_add_dict_u64() != 0
> +	|| test_case_add_dict_string() != 0
> +	||  test_dict_with_array_int_values() != 0
> +	|| test_dict_with_array_u64_values() != 0
> +	|| test_dict_with_array_string_values() != 0
> +	|| test_array_with_array_int_values() != 0
> +	|| test_array_with_array_u64_values() != 0
> +	|| test_array_with_array_string_values() != 0) {
> +		close(sock);
> +		return -1;
> +	}

I'm not sure I like this way of calling the test cases, and the indentation
of the lines is definitely wrong. The simplest option here is to have a
single if statement for each test and a goto for the "error" leg:

	if (test_case_array_string() != 0)
	   goto error;
	if (test_case_array_int() != 0)
	   goto error;
	...

However, if you want to avoid the multiple if statements, a better option
than merging as above would be to put the functions in an array and loop
over them as (rough code, completely untested!):

	typedef int (*test_case)(void);

	test_case test_cases[] = {test_case_array_string,
			test_case_array_int, ... };
	...
	for (i = 0; i < RTE_DIM(test_cases); i++)
		if (test_cases[i]() != 0) {
			...
		}

> +
> +	close(sock);
> +	return 0;
> +}
> +
> +REGISTER_TEST_COMMAND(telemetry_data_autotest, test_telemetry_data);
> -- 

Regards,
/Bruce

^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v6 0/3] add basic ethdev stats with data object recursion
  2020-06-12 10:53 [dpdk-dev] [RFC 0/2] add basic ethdev stats with data object recursion Ciara Power
                   ` (5 preceding siblings ...)
  2020-07-15 12:29 ` [dpdk-dev] [PATCH v5 0/3] add basic ethdev stats with data object recursion Ciara Power
@ 2020-07-20 11:19 ` Ciara Power
  2020-07-20 11:19   ` [dpdk-dev] [PATCH v6 1/3] telemetry: support array values in data objects Ciara Power
                     ` (2 more replies)
  2020-07-20 14:04 ` [dpdk-dev] [PATCH v7 0/3] add basic ethdev stats with data object recursion Ciara Power
                   ` (2 subsequent siblings)
  9 siblings, 3 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-20 11:19 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, bruce.richardson, Ciara Power

v6:
  - Fixed FreeBSD build failure for unit tests.
  - Added comments and expanded commit log.
  - Add loop to call test cases stored in a list.
v5: Added unit tests for telemetry data to JSON conversion.
v4: Added missing param description to Doxygen comment.
v3:
  - Modified commit logs.
  - Changed function names to reference container.
  - Modified Doxygen comments to reference container.
v2:
  - Added support for arrays to have container values.
  - Added support for int and string arrays within dict/array.
  - Added APIs for internal container memory management.

This patchset adds support for basic ethdev statistics in Telemetry.
To do this, recursive data object support is needed to report the queue
statistics in a list. With this patch, an array or dictionary supports
uint64_t, int or string array type values, which is used for the ethdev
queue stats.

Ciara Power (2):
  telemetry: support array values in data objects
  ethdev: add common stats for telemetry

Louise Kilheeney (1):
  test/test_telemetry_data: add unit tests for data to JSON

 app/test/Makefile                             |   1 +
 app/test/meson.build                          |   5 +-
 app/test/test_telemetry_data.c                | 375 ++++++++++++++++++
 lib/librte_ethdev/rte_ethdev.c                |  53 +++
 lib/librte_telemetry/rte_telemetry.h          |  70 ++++
 .../rte_telemetry_version.map                 |   4 +
 lib/librte_telemetry/telemetry.c              |  56 +++
 lib/librte_telemetry/telemetry_data.c         |  51 +++
 lib/librte_telemetry/telemetry_data.h         |   7 +
 lib/librte_telemetry/telemetry_json.h         |  33 ++
 10 files changed, 653 insertions(+), 2 deletions(-)
 create mode 100644 app/test/test_telemetry_data.c

-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v6 1/3] telemetry: support array values in data objects
  2020-07-20 11:19 ` [dpdk-dev] [PATCH v6 0/3] add basic ethdev stats with data object recursion Ciara Power
@ 2020-07-20 11:19   ` Ciara Power
  2020-07-20 11:19   ` [dpdk-dev] [PATCH v6 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
  2020-07-20 11:19   ` [dpdk-dev] [PATCH v6 3/3] ethdev: add common stats for telemetry Ciara Power
  2 siblings, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-20 11:19 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, bruce.richardson, Ciara Power

Arrays of type uint64_t/int/string can now be included within an array
or dict. One level of embedded containers is supported. This is
necessary to allow for instances such as the ethdev queue stats to be
reported as a list of uint64_t values, rather than having multiple dict
entries with one uint64_t value for each queue stat.

The memory management APIs provided by telemetry simplify the memory
allocation/free aspect of the embedded container. The rte_tel_data_alloc
function is called in the library/app callback to return a pointer to a
container that has been allocated memory. When adding this container
to an array/dict, a parameter is passed to indicate if the memory
should be freed by telemetry after use. This will allow reuse of the
allocated memory if the library/app wishes to do so.

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

---
v4: Added missing param description to Doxygen comment.
v3:
  - Modified commit log.
  - Changed function name to reference container.
  - Modified Doxygen comments to reference container.
v2:
  - Added support for arrays to have container values.
  - Added support for int and string arrays within dict/array.
  - Added APIs for internal container memory management.
---
 lib/librte_telemetry/rte_telemetry.h          | 70 +++++++++++++++++++
 .../rte_telemetry_version.map                 |  4 ++
 lib/librte_telemetry/telemetry.c              | 56 +++++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 51 ++++++++++++++
 lib/librte_telemetry/telemetry_data.h         |  7 ++
 lib/librte_telemetry/telemetry_json.h         | 33 +++++++++
 6 files changed, 221 insertions(+)

diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
index d13010b8f..c16541226 100644
--- a/lib/librte_telemetry/rte_telemetry.h
+++ b/lib/librte_telemetry/rte_telemetry.h
@@ -46,6 +46,7 @@ enum rte_tel_value_type {
 	RTE_TEL_STRING_VAL, /** a string value */
 	RTE_TEL_INT_VAL,    /** a signed 32-bit int value */
 	RTE_TEL_U64_VAL,    /** an unsigned 64-bit int value */
+	RTE_TEL_CONTAINER, /** a container struct */
 };
 
 /**
@@ -136,6 +137,28 @@ __rte_experimental
 int
 rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x);
 
+/**
+ * Add a container to an array. A container is an existing telemetry data
+ * array. The array the container is to be added to must have been started by
+ * rte_tel_data_start_array() with RTE_TEL_CONTAINER as the type parameter.
+ * The container type must be an array of type uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param val
+ *   The pointer to the container to be stored in the array.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep);
+
 /**
  * Add a string value to a dictionary.
  * The dict must have been started by rte_tel_data_start_dict().
@@ -190,6 +213,30 @@ int
 rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 		const char *name, uint64_t val);
 
+/**
+ * Add a container to a dictionary. A container is an existing telemetry data
+ * array. The dict the container is to be added to must have been started by
+ * rte_tel_data_start_dict(). The container must be an array of type
+ * uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param name
+ *   The name the value is to be stored under in the dict.
+ * @param val
+ *   The pointer to the container to be stored in the dict.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep);
+
 /**
  * This telemetry callback is used when registering a telemetry command.
  * It handles getting and formatting information to be returned to telemetry
@@ -264,4 +311,27 @@ int
 rte_telemetry_init(const char *runtime_dir, rte_cpuset_t *cpuset,
 		const char **err_str);
 
+/**
+ * Get a pointer to a container with memory allocated. The container is to be
+ * used embedded within an existing telemetry dict/array.
+ *
+ * @return
+ *  Pointer to a container.
+ */
+__rte_experimental
+struct rte_tel_data *
+rte_tel_data_alloc(void);
+
+/**
+ * @internal
+ * Free a container that has memory allocated.
+ *
+ * @param data
+ *  Pointer to container.
+ *.
+ */
+__rte_experimental
+void
+rte_tel_data_free(struct rte_tel_data *data);
+
 #endif
diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map
index 86433c21d..d1dbf8d58 100644
--- a/lib/librte_telemetry/rte_telemetry_version.map
+++ b/lib/librte_telemetry/rte_telemetry_version.map
@@ -1,12 +1,16 @@
 EXPERIMENTAL {
 	global:
 
+	rte_tel_data_add_array_container;
 	rte_tel_data_add_array_int;
 	rte_tel_data_add_array_string;
 	rte_tel_data_add_array_u64;
+	rte_tel_data_add_dict_container;
 	rte_tel_data_add_dict_int;
 	rte_tel_data_add_dict_string;
 	rte_tel_data_add_dict_u64;
+	rte_tel_data_alloc;
+	rte_tel_data_free;
 	rte_tel_data_start_array;
 	rte_tel_data_start_dict;
 	rte_tel_data_string;
diff --git a/lib/librte_telemetry/telemetry.c b/lib/librte_telemetry/telemetry.c
index 025228273..4469885c3 100644
--- a/lib/librte_telemetry/telemetry.c
+++ b/lib/librte_telemetry/telemetry.c
@@ -123,6 +123,35 @@ command_help(const char *cmd __rte_unused, const char *params,
 	return 0;
 }
 
+static int
+container_to_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len)
+{
+	size_t used = 0;
+	unsigned int i;
+
+	if (d->type != RTE_TEL_ARRAY_U64 && d->type != RTE_TEL_ARRAY_INT
+			&& d->type != RTE_TEL_ARRAY_STRING)
+		return snprintf(out_buf, buf_len, "null");
+
+	used = rte_tel_json_empty_array(out_buf, buf_len, 0);
+	if (d->type == RTE_TEL_ARRAY_U64)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_u64(out_buf,
+				buf_len, used,
+				d->data.array[i].u64val);
+	if (d->type == RTE_TEL_ARRAY_INT)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_int(out_buf,
+				buf_len, used,
+				d->data.array[i].ival);
+	if (d->type == RTE_TEL_ARRAY_STRING)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_string(out_buf,
+				buf_len, used,
+				d->data.array[i].sval);
+	return used;
+}
+
 static void
 output_json(const char *cmd, const struct rte_tel_data *d, int s)
 {
@@ -169,6 +198,20 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 						buf_len, used,
 						v->name, v->value.u64val);
 				break;
+			case RTE_TEL_CONTAINER:
+			{
+				char temp[buf_len];
+				const struct container *cont =
+						&v->value.container;
+				if (container_to_json(cont->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_obj_json(
+							cb_data_buf,
+							buf_len, used,
+							v->name, temp);
+				if (!cont->keep)
+					rte_tel_data_free(cont->data);
+			}
 			}
 		}
 		used += prefix_used;
@@ -177,6 +220,7 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 	case RTE_TEL_ARRAY_STRING:
 	case RTE_TEL_ARRAY_INT:
 	case RTE_TEL_ARRAY_U64:
+	case RTE_TEL_ARRAY_CONTAINER:
 		prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
 				MAX_CMD_LEN, cmd);
 		cb_data_buf = &out_buf[prefix_used];
@@ -197,6 +241,18 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 				used = rte_tel_json_add_array_u64(cb_data_buf,
 						buf_len, used,
 						d->data.array[i].u64val);
+			else if (d->type == RTE_TEL_ARRAY_CONTAINER) {
+				char temp[buf_len];
+				const struct container *rec_data =
+						&d->data.array[i].container;
+				if (container_to_json(rec_data->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_array_json(
+							cb_data_buf,
+							buf_len, used, temp);
+				if (!rec_data->keep)
+					rte_tel_data_free(rec_data->data);
+			}
 		used += prefix_used;
 		used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
 		break;
diff --git a/lib/librte_telemetry/telemetry_data.c b/lib/librte_telemetry/telemetry_data.c
index f424bbd48..77b0fe09a 100644
--- a/lib/librte_telemetry/telemetry_data.c
+++ b/lib/librte_telemetry/telemetry_data.c
@@ -14,6 +14,7 @@ rte_tel_data_start_array(struct rte_tel_data *d, enum rte_tel_value_type type)
 			RTE_TEL_ARRAY_STRING, /* RTE_TEL_STRING_VAL = 0 */
 			RTE_TEL_ARRAY_INT,    /* RTE_TEL_INT_VAL = 1 */
 			RTE_TEL_ARRAY_U64,    /* RTE_TEL_u64_VAL = 2 */
+			RTE_TEL_ARRAY_CONTAINER, /* RTE_TEL_CONTAINER = 3 */
 	};
 	d->type = array_types[type];
 	d->data_len = 0;
@@ -74,6 +75,23 @@ rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x)
 	return 0;
 }
 
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep)
+{
+	if (d->type != RTE_TEL_ARRAY_CONTAINER ||
+			(val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_ARRAY_ENTRIES)
+		return -ENOSPC;
+
+	d->data.array[d->data_len].container.data = val;
+	d->data.array[d->data_len++].container.keep = !!keep;
+	return 0;
+}
+
 int
 rte_tel_data_add_dict_string(struct rte_tel_data *d, const char *name,
 		const char *val)
@@ -128,3 +146,36 @@ rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
 	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
 }
+
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep)
+{
+	struct tel_dict_entry *e = &d->data.dict[d->data_len];
+
+	if (d->type != RTE_TEL_DICT || (val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES)
+		return -ENOSPC;
+
+	d->data_len++;
+	e->type = RTE_TEL_CONTAINER;
+	e->value.container.data = val;
+	e->value.container.keep = !!keep;
+	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
+	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
+}
+
+struct rte_tel_data *
+rte_tel_data_alloc(void)
+{
+	return malloc(sizeof(struct rte_tel_data));
+}
+
+void
+rte_tel_data_free(struct rte_tel_data *data)
+{
+	free(data);
+}
diff --git a/lib/librte_telemetry/telemetry_data.h b/lib/librte_telemetry/telemetry_data.h
index ff3a371a3..adb84a09f 100644
--- a/lib/librte_telemetry/telemetry_data.h
+++ b/lib/librte_telemetry/telemetry_data.h
@@ -15,6 +15,12 @@ enum tel_container_types {
 	RTE_TEL_ARRAY_STRING, /** array of string values only */
 	RTE_TEL_ARRAY_INT,    /** array of signed, 32-bit int values */
 	RTE_TEL_ARRAY_U64,    /** array of unsigned 64-bit int values */
+	RTE_TEL_ARRAY_CONTAINER, /** array of container structs */
+};
+
+struct container {
+	struct rte_tel_data *data;
+	int keep;
 };
 
 /* each type here must have an equivalent enum in the value types enum in
@@ -25,6 +31,7 @@ union tel_value {
 	char sval[RTE_TEL_MAX_STRING_LEN];
 	int ival;
 	uint64_t u64val;
+	struct container container;
 };
 
 struct tel_dict_entry {
diff --git a/lib/librte_telemetry/telemetry_json.h b/lib/librte_telemetry/telemetry_json.h
index a2ce4899e..ad270b9b3 100644
--- a/lib/librte_telemetry/telemetry_json.h
+++ b/lib/librte_telemetry/telemetry_json.h
@@ -102,6 +102,22 @@ rte_tel_json_add_array_u64(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/*
+ * Add a new element with raw JSON value to the JSON array stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_array_json(char *buf, const int len, const int used,
+		const char *str)
+{
+	int ret, end = used - 1; /* strip off final delimiter */
+	if (used <= 2) /* assume empty, since minimum is '[]' */
+		return __json_snprintf(buf, len, "[%s]", str);
+
+	ret = __json_snprintf(buf + end, len - end, ",%s]", str);
+	return ret == 0 ? used : end + ret;
+}
+
 /**
  * Add a new element with uint64_t value to the JSON object stored in the
  * provided buffer.
@@ -155,4 +171,21 @@ rte_tel_json_add_obj_str(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/**
+ * Add a new element with raw JSON value to the JSON object stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_obj_json(char *buf, const int len, const int used,
+		const char *name, const char *val)
+{
+	int ret, end = used - 1;
+	if (used <= 2) /* assume empty, since minimum is '{}' */
+		return __json_snprintf(buf, len, "{\"%s\":%s}", name, val);
+
+	ret = __json_snprintf(buf + end, len - end, ",\"%s\":%s}",
+			name, val);
+	return ret == 0 ? used : end + ret;
+}
+
 #endif /*_RTE_TELEMETRY_JSON_H_*/
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v6 2/3] test/test_telemetry_data: add unit tests for data to JSON
  2020-07-20 11:19 ` [dpdk-dev] [PATCH v6 0/3] add basic ethdev stats with data object recursion Ciara Power
  2020-07-20 11:19   ` [dpdk-dev] [PATCH v6 1/3] telemetry: support array values in data objects Ciara Power
@ 2020-07-20 11:19   ` Ciara Power
  2020-07-20 13:08     ` Bruce Richardson
  2020-07-20 11:19   ` [dpdk-dev] [PATCH v6 3/3] ethdev: add common stats for telemetry Ciara Power
  2 siblings, 1 reply; 44+ messages in thread
From: Ciara Power @ 2020-07-20 11:19 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, bruce.richardson, Louise Kilheeney, Ciara Power

From: Louise Kilheeney <louise.kilheeney@intel.com>

This patch adds tests for verifying telemetry data structures are
converted to JSON as expected. Both flat and recursive data structures
are tested, for all possible value types.

The app connects to the telemetry socket as a client, and registers one
command with a corresponding callback function. Each time the callback
function is called, it copies a global data variable to the data pointer
passed in by telemetry.
When a test case is run, the test case function builds up the global
data variable with the relevant data types, and the expected json string
output which should be generated from that. The 'test_output()' function
is used to trigger the callback and ensure the actual output matches
that expected.

Signed-off-by: Louise Kilheeney <louise.kilheeney@intel.com>
Signed-off-by: Ciara Power <ciara.power@intel.com>

---
v6:
  - Fixed FreeBSD build error.
  - Added comments and expanded commit log.
  - Add loop to call test cases stored in a list.
---
 app/test/Makefile              |   1 +
 app/test/meson.build           |   5 +-
 app/test/test_telemetry_data.c | 375 +++++++++++++++++++++++++++++++++
 3 files changed, 379 insertions(+), 2 deletions(-)
 create mode 100644 app/test/test_telemetry_data.c

diff --git a/app/test/Makefile b/app/test/Makefile
index f4065271e..1cb64089c 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -145,6 +145,7 @@ SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6.c
 SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6_perf.c
 
 SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += test_telemetry_json.c
+SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += test_telemetry_data.c
 
 SRCS-y += test_debug.c
 SRCS-y += test_errno.c
diff --git a/app/test/meson.build b/app/test/meson.build
index 786a21397..4a72fe5b6 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -170,6 +170,7 @@ test_deps = ['acl',
 	'ring',
 	'security',
 	'stack',
+	'telemetry',
 	'timer'
 ]
 
@@ -345,8 +346,8 @@ if dpdk_conf.has('RTE_LIBRTE_SKELETON_EVENTDEV_PMD')
 	test_deps += 'pmd_skeleton_event'
 endif
 if dpdk_conf.has('RTE_LIBRTE_TELEMETRY')
-	test_sources += 'test_telemetry_json.c'
-	fast_tests += [['telemetry_json_autotest', true]]
+	test_sources += ['test_telemetry_json.c', 'test_telemetry_data.c']
+	fast_tests += [['telemetry_json_autotest', true], ['telemetry_data_autotest', true]]
 endif
 
 # The following linkages of drivers are required because
diff --git a/app/test/test_telemetry_data.c b/app/test/test_telemetry_data.c
new file mode 100644
index 000000000..f95054433
--- /dev/null
+++ b/app/test/test_telemetry_data.c
@@ -0,0 +1,375 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2020 Intel Corporation
+ */
+
+#include <glob.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <rte_eal.h>
+#include <rte_common.h>
+#include <rte_telemetry.h>
+#include <rte_string_fns.h>
+
+#include "test.h"
+#include "telemetry_data.h"
+
+#define TELEMETRY_VERSION "v2"
+#define REQUEST_CMD "/test"
+#define BUF_SIZE 1024
+#define TEST_OUTPUT(exp) test_output(__func__, exp)
+
+static struct rte_tel_data response_data;
+static int sock;
+
+/*
+ * This function is the callback registered with Telemetry to be used when
+ * the /test command is requested. This callback returns the global data built
+ * up by the individual test cases.
+ */
+static int
+test_cb(const char *cmd __rte_unused, const char *params __rte_unused,
+		struct rte_tel_data *d)
+{
+	*d = response_data;
+	return 0;
+}
+
+/*
+ * This function is called by each test case function. It communicates with
+ * the telemetry socket by requesting the /test command, and reading the
+ * response. The expected response is passed in by the test case function,
+ * and is compared to the actual response received from Telemetry.
+ */
+static int
+test_output(const char *func_name, const char *expected)
+{
+	int bytes;
+	char buf[BUF_SIZE * 16];
+	if (write(sock, REQUEST_CMD, strlen(REQUEST_CMD)) < 0) {
+		printf("%s: Error with socket write - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	bytes = read(sock, buf, sizeof(buf));
+	if (bytes < 0) {
+		printf("%s: Error with socket read - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	buf[bytes] = '\0';
+	printf("%s: buf = '%s', expected = '%s'\n", func_name, buf, expected);
+	return strncmp(expected, buf, sizeof(buf));
+}
+
+static int
+test_dict_with_array_int_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_INT_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_INT_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_int(child_data, i);
+		rte_tel_data_add_array_int(child_data2, i);
+	}
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[0,1,2,3,4],\"dict_1\":[0,1,2,3,4]}}");
+}
+
+static int
+test_array_with_array_int_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_INT_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_INT_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_int(child_data, i);
+		rte_tel_data_add_array_int(child_data2, i);
+	}
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[0,1,2,3,4],[0,1,2,3,4]]}");
+}
+
+static int
+test_case_array_int(void)
+{
+	int i;
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_INT_VAL);
+	for (i = 0; i < 5; i++)
+		rte_tel_data_add_array_int(&response_data, i);
+	return TEST_OUTPUT("{\"/test\":[0,1,2,3,4]}");
+}
+
+static int
+test_case_add_dict_int(void)
+{
+	int i = 0;
+	char name_of_value[8];
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		sprintf(name_of_value, "dict_%d", i);
+		rte_tel_data_add_dict_int(&response_data, name_of_value, i);
+	}
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":0,\"dict_1\":1,\"dict_2\":2,\"dict_3\":3,\"dict_4\":4}}");
+}
+
+static int
+test_case_array_string(void)
+{
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_STRING_VAL);
+	rte_tel_data_add_array_string(&response_data, "aaaa");
+	rte_tel_data_add_array_string(&response_data, "bbbb");
+	rte_tel_data_add_array_string(&response_data, "cccc");
+	rte_tel_data_add_array_string(&response_data, "dddd");
+	rte_tel_data_add_array_string(&response_data, "eeee");
+
+	return TEST_OUTPUT("{\"/test\":[\"aaaa\",\"bbbb\",\"cccc\",\"dddd\",\"eeee\"]}");
+}
+
+static int
+test_case_add_dict_string(void)
+{
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	rte_tel_data_add_dict_string(&response_data, "dict_0", "aaaa");
+	rte_tel_data_add_dict_string(&response_data, "dict_1", "bbbb");
+	rte_tel_data_add_dict_string(&response_data, "dict_2", "cccc");
+	rte_tel_data_add_dict_string(&response_data, "dict_3", "dddd");
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":\"aaaa\",\"dict_1\":\"bbbb\",\"dict_2\":\"cccc\",\"dict_3\":\"dddd\"}}");
+}
+
+
+static int
+test_dict_with_array_string_values(void)
+{
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_STRING_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_STRING_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	rte_tel_data_add_array_string(child_data, "aaaa");
+	rte_tel_data_add_array_string(child_data2, "bbbb");
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[\"aaaa\"],\"dict_1\":[\"bbbb\"]}}");
+}
+
+static int
+test_array_with_array_string_values(void)
+{
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_STRING_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_STRING_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	rte_tel_data_add_array_string(child_data, "aaaa");
+	rte_tel_data_add_array_string(child_data2, "bbbb");
+
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[\"aaaa\"],[\"bbbb\"]]}");
+}
+
+static int
+test_case_array_u64(void)
+{
+	int i;
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_U64_VAL);
+	for (i = 0; i < 5; i++)
+		rte_tel_data_add_array_u64(&response_data, i);
+	return TEST_OUTPUT("{\"/test\":[0,1,2,3,4]}");
+}
+
+static int
+test_case_add_dict_u64(void)
+{
+	int i = 0;
+	char name_of_value[8];
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		sprintf(name_of_value, "dict_%d", i);
+		rte_tel_data_add_dict_u64(&response_data, name_of_value, i);
+	}
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":0,\"dict_1\":1,\"dict_2\":2,\"dict_3\":3,\"dict_4\":4}}");
+}
+
+static int
+test_dict_with_array_u64_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_U64_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_U64_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 10; i++) {
+		rte_tel_data_add_array_u64(child_data, i);
+		rte_tel_data_add_array_u64(child_data2, i);
+	}
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[0,1,2,3,4,5,6,7,8,9],\"dict_1\":[0,1,2,3,4,5,6,7,8,9]}}");
+}
+
+static int
+test_array_with_array_u64_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_U64_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_U64_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_u64(child_data, i);
+		rte_tel_data_add_array_u64(child_data2, i);
+	}
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[0,1,2,3,4],[0,1,2,3,4]]}");
+}
+
+static int
+connect_to_socket(void)
+{
+	char file_pattern[PATH_MAX];
+	char buf[BUF_SIZE];
+	glob_t globbuf;
+	int sock, bytes;
+	struct sockaddr_un telem_addr;
+
+	snprintf(file_pattern, sizeof(file_pattern),
+			"%s/dpdk_telemetry.%s",	rte_eal_get_runtime_dir(),
+			TELEMETRY_VERSION);
+	if (glob(file_pattern, 0, NULL, &globbuf) != 0) {
+		printf("\n%s: Error finding socket file path: %s\n", __func__,
+				strerror(errno));
+		globfree(&globbuf);
+		return -1;
+	}
+
+	sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+	if (sock < 0) {
+		printf("\n%s: Error creating socket: %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	telem_addr.sun_family = AF_UNIX;
+	strlcpy(telem_addr.sun_path, globbuf.gl_pathv[0],
+			sizeof(telem_addr.sun_path));
+	if (connect(sock, (struct sockaddr *) &telem_addr,
+			sizeof(telem_addr)) < 0)
+		printf("\n%s: Error connecting to socket: %s\n", __func__,
+				strerror(errno));
+
+	bytes = read(sock, buf, sizeof(buf));
+	if (bytes < 0) {
+		printf("%s: Error with socket read - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	buf[bytes] = '\0';
+	printf("\n%s: %s\n", __func__, buf);
+	globfree(&globbuf);
+	return sock;
+}
+
+static int
+test_telemetry_data(void)
+{
+	typedef int (*test_case)(void);
+	unsigned int i = 0;
+
+	sock = connect_to_socket();
+	if (sock <= 0)
+		return -1;
+
+	test_case test_cases[] = {test_case_array_string,
+			test_case_array_int, test_case_array_u64,
+			test_case_add_dict_int, test_case_add_dict_u64,
+			test_case_add_dict_string,
+			test_dict_with_array_int_values,
+			test_dict_with_array_u64_values,
+			test_dict_with_array_string_values,
+			test_array_with_array_int_values,
+			test_array_with_array_u64_values,
+			test_array_with_array_string_values };
+
+	rte_telemetry_register_cmd(REQUEST_CMD, test_cb, "Test");
+	for (i = 0; i < RTE_DIM(test_cases); i++) {
+		if (test_cases[i]() != 0) {
+			close(sock);
+			return -1;
+		}
+	}
+	close(sock);
+	return 0;
+}
+
+REGISTER_TEST_COMMAND(telemetry_data_autotest, test_telemetry_data);
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v6 3/3] ethdev: add common stats for telemetry
  2020-07-20 11:19 ` [dpdk-dev] [PATCH v6 0/3] add basic ethdev stats with data object recursion Ciara Power
  2020-07-20 11:19   ` [dpdk-dev] [PATCH v6 1/3] telemetry: support array values in data objects Ciara Power
  2020-07-20 11:19   ` [dpdk-dev] [PATCH v6 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
@ 2020-07-20 11:19   ` Ciara Power
  2 siblings, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-20 11:19 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, bruce.richardson, Ciara Power

The ethdev library now registers a telemetry command for common ethdev
statistics.

An example usage is shown below:

Connecting to /var/run/dpdk/rte/dpdk_telemetry.v2
{"version": "DPDK 20.08.0-rc1", "pid": 14119, "max_output_len": 16384}
--> /ethdev/stats,0
{"/ethdev/stats": {"ipackets": 0, "opackets": 0, "ibytes": 0, "obytes": \
    0, "imissed": 0, "ierrors": 0, "oerrors": 0, "rx_nombuf": 0, \
    "q_ipackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_opackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_ibytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_obytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_errors": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}}

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

---
v3:
  - Updated help text and commit subject line.
v2:
  - Updated to use memory management APIs.
---
 lib/librte_ethdev/rte_ethdev.c | 53 ++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
index 7858ad5f1..8ee14b1b5 100644
--- a/lib/librte_ethdev/rte_ethdev.c
+++ b/lib/librte_ethdev/rte_ethdev.c
@@ -5275,6 +5275,57 @@ handle_port_list(const char *cmd __rte_unused,
 	return 0;
 }
 
+static void
+add_port_queue_stats(struct rte_tel_data *d, uint64_t *q_stats,
+		const char *stat_name)
+{
+	int q;
+	struct rte_tel_data *q_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(q_data, RTE_TEL_U64_VAL);
+	for (q = 0; q < RTE_ETHDEV_QUEUE_STAT_CNTRS; q++)
+		rte_tel_data_add_array_u64(q_data, q_stats[q]);
+	rte_tel_data_add_dict_container(d, stat_name, q_data, 0);
+}
+
+#define ADD_DICT_STAT(stats, s) rte_tel_data_add_dict_u64(d, #s, stats.s)
+
+static int
+handle_port_stats(const char *cmd __rte_unused,
+		const char *params,
+		struct rte_tel_data *d)
+{
+	struct rte_eth_stats stats;
+	int port_id, ret;
+
+	if (params == NULL || strlen(params) == 0 || !isdigit(*params))
+		return -1;
+
+	port_id = atoi(params);
+	if (!rte_eth_dev_is_valid_port(port_id))
+		return -1;
+
+	ret = rte_eth_stats_get(port_id, &stats);
+	if (ret < 0)
+		return -1;
+
+	rte_tel_data_start_dict(d);
+	ADD_DICT_STAT(stats, ipackets);
+	ADD_DICT_STAT(stats, opackets);
+	ADD_DICT_STAT(stats, ibytes);
+	ADD_DICT_STAT(stats, obytes);
+	ADD_DICT_STAT(stats, imissed);
+	ADD_DICT_STAT(stats, ierrors);
+	ADD_DICT_STAT(stats, oerrors);
+	ADD_DICT_STAT(stats, rx_nombuf);
+	add_port_queue_stats(d, stats.q_ipackets, "q_ipackets");
+	add_port_queue_stats(d, stats.q_opackets, "q_opackets");
+	add_port_queue_stats(d, stats.q_ibytes, "q_ibytes");
+	add_port_queue_stats(d, stats.q_obytes, "q_obytes");
+	add_port_queue_stats(d, stats.q_errors, "q_errors");
+
+	return 0;
+}
+
 static int
 handle_port_xstats(const char *cmd __rte_unused,
 		const char *params,
@@ -5361,6 +5412,8 @@ RTE_INIT(ethdev_init_telemetry)
 {
 	rte_telemetry_register_cmd("/ethdev/list", handle_port_list,
 			"Returns list of available ethdev ports. Takes no parameters");
+	rte_telemetry_register_cmd("/ethdev/stats", handle_port_stats,
+			"Returns the common stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/xstats", handle_port_xstats,
 			"Returns the extended stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/link_status",
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [dpdk-dev] [PATCH v6 2/3] test/test_telemetry_data: add unit tests for data to JSON
  2020-07-20 11:19   ` [dpdk-dev] [PATCH v6 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
@ 2020-07-20 13:08     ` Bruce Richardson
  0 siblings, 0 replies; 44+ messages in thread
From: Bruce Richardson @ 2020-07-20 13:08 UTC (permalink / raw)
  To: Ciara Power
  Cc: kevin.laatz, thomas, ferruh.yigit, arybchenko, dev, keith.wiles,
	Louise Kilheeney

On Mon, Jul 20, 2020 at 12:19:06PM +0100, Ciara Power wrote:
> From: Louise Kilheeney <louise.kilheeney@intel.com>
> 
> This patch adds tests for verifying telemetry data structures are
> converted to JSON as expected. Both flat and recursive data structures
> are tested, for all possible value types.
> 
> The app connects to the telemetry socket as a client, and registers one
> command with a corresponding callback function. Each time the callback
> function is called, it copies a global data variable to the data pointer
> passed in by telemetry.
> When a test case is run, the test case function builds up the global
> data variable with the relevant data types, and the expected json string
> output which should be generated from that. The 'test_output()' function
> is used to trigger the callback and ensure the actual output matches
> that expected.
> 
> Signed-off-by: Louise Kilheeney <louise.kilheeney@intel.com>
> Signed-off-by: Ciara Power <ciara.power@intel.com>
> 
> ---
> v6:
>   - Fixed FreeBSD build error.
>   - Added comments and expanded commit log.
>   - Add loop to call test cases stored in a list.
> ---

Thanks, this is a good improvement. Apologies but a few more comments below
that I missed in the previous version.

/Bruce

>  app/test/Makefile              |   1 +
>  app/test/meson.build           |   5 +-
>  app/test/test_telemetry_data.c | 375 +++++++++++++++++++++++++++++++++
>  3 files changed, 379 insertions(+), 2 deletions(-)
>  create mode 100644 app/test/test_telemetry_data.c
> 
> diff --git a/app/test/Makefile b/app/test/Makefile
> index f4065271e..1cb64089c 100644
> --- a/app/test/Makefile
> +++ b/app/test/Makefile
> @@ -145,6 +145,7 @@ SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6.c
>  SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6_perf.c
>  
>  SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += test_telemetry_json.c
> +SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += test_telemetry_data.c
>  
>  SRCS-y += test_debug.c
>  SRCS-y += test_errno.c
> diff --git a/app/test/meson.build b/app/test/meson.build
> index 786a21397..4a72fe5b6 100644
> --- a/app/test/meson.build
> +++ b/app/test/meson.build
> @@ -170,6 +170,7 @@ test_deps = ['acl',
>  	'ring',
>  	'security',
>  	'stack',
> +	'telemetry',
>  	'timer'
>  ]
>  
> @@ -345,8 +346,8 @@ if dpdk_conf.has('RTE_LIBRTE_SKELETON_EVENTDEV_PMD')
>  	test_deps += 'pmd_skeleton_event'
>  endif
>  if dpdk_conf.has('RTE_LIBRTE_TELEMETRY')
> -	test_sources += 'test_telemetry_json.c'
> -	fast_tests += [['telemetry_json_autotest', true]]
> +	test_sources += ['test_telemetry_json.c', 'test_telemetry_data.c']
> +	fast_tests += [['telemetry_json_autotest', true], ['telemetry_data_autotest', true]]
>  endif
>  
>  # The following linkages of drivers are required because
> diff --git a/app/test/test_telemetry_data.c b/app/test/test_telemetry_data.c
> new file mode 100644
> index 000000000..f95054433
> --- /dev/null
> +++ b/app/test/test_telemetry_data.c
> @@ -0,0 +1,375 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright 2020 Intel Corporation
> + */
> +
> +#include <glob.h>

While the python script for testing telemetry does use glob to find the
socket, in this case I think it's unnecessary since we know we are in the
process that created the listening socket, so we can just connect straight
to it.

> +#include <string.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
> +#include <unistd.h>
> +#include <limits.h>
> +
> +#include <rte_eal.h>
> +#include <rte_common.h>
> +#include <rte_telemetry.h>
> +#include <rte_string_fns.h>
> +
> +#include "test.h"
> +#include "telemetry_data.h"
> +
> +#define TELEMETRY_VERSION "v2"
> +#define REQUEST_CMD "/test"
> +#define BUF_SIZE 1024
> +#define TEST_OUTPUT(exp) test_output(__func__, exp)
> +
> +static struct rte_tel_data response_data;
> +static int sock;
> +
> +/*
> + * This function is the callback registered with Telemetry to be used when
> + * the /test command is requested. This callback returns the global data built
> + * up by the individual test cases.
> + */
> +static int
> +test_cb(const char *cmd __rte_unused, const char *params __rte_unused,
> +		struct rte_tel_data *d)
> +{
> +	*d = response_data;
> +	return 0;
> +}
> +
> +/*
> + * This function is called by each test case function. It communicates with
> + * the telemetry socket by requesting the /test command, and reading the
> + * response. The expected response is passed in by the test case function,
> + * and is compared to the actual response received from Telemetry.
> + */
> +static int
> +test_output(const char *func_name, const char *expected)
> +{
> +	int bytes;
> +	char buf[BUF_SIZE * 16];
> +	if (write(sock, REQUEST_CMD, strlen(REQUEST_CMD)) < 0) {
> +		printf("%s: Error with socket write - %s\n", __func__,
> +				strerror(errno));
> +		return -1;
> +	}
> +	bytes = read(sock, buf, sizeof(buf));
> +	if (bytes < 0) {
> +		printf("%s: Error with socket read - %s\n", __func__,
> +				strerror(errno));
> +		return -1;
> +	}
> +	buf[bytes] = '\0';

Need to check here for overflow. If bytes == BUF_SIZE * 16, you'll
overflow.

> +	printf("%s: buf = '%s', expected = '%s'\n", func_name, buf, expected);
> +	return strncmp(expected, buf, sizeof(buf));
> +}
> +
> +static int
> +test_dict_with_array_int_values(void)
> +{
> +	int i;
> +
> +	struct rte_tel_data *child_data = rte_tel_data_alloc();
> +	rte_tel_data_start_array(child_data, RTE_TEL_INT_VAL);
> +
> +	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
> +	rte_tel_data_start_array(child_data2, RTE_TEL_INT_VAL);
> +
> +	memset(&response_data, 0, sizeof(response_data));
> +	rte_tel_data_start_dict(&response_data);
> +
> +	for (i = 0; i < 5; i++) {
> +		rte_tel_data_add_array_int(child_data, i);
> +		rte_tel_data_add_array_int(child_data2, i);
> +	}
> +
> +	rte_tel_data_add_dict_container(&response_data, "dict_0",
> +	 child_data, 0);
> +	rte_tel_data_add_dict_container(&response_data, "dict_1",
> +	 child_data2, 0);
> +
> +	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[0,1,2,3,4],\"dict_1\":[0,1,2,3,4]}}");
> +}
> +
> +static int
> +test_array_with_array_int_values(void)
> +{
> +	int i;
> +
> +	struct rte_tel_data *child_data = rte_tel_data_alloc();
> +	rte_tel_data_start_array(child_data, RTE_TEL_INT_VAL);
> +
> +	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
> +	rte_tel_data_start_array(child_data2, RTE_TEL_INT_VAL);
> +
> +	memset(&response_data, 0, sizeof(response_data));
> +	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
> +
> +	for (i = 0; i < 5; i++) {
> +		rte_tel_data_add_array_int(child_data, i);
> +		rte_tel_data_add_array_int(child_data2, i);
> +	}
> +	rte_tel_data_add_array_container(&response_data, child_data, 0);
> +	rte_tel_data_add_array_container(&response_data, child_data2, 0);
> +
> +	return TEST_OUTPUT("{\"/test\":[[0,1,2,3,4],[0,1,2,3,4]]}");
> +}
> +
> +static int
> +test_case_array_int(void)
> +{
> +	int i;
> +	memset(&response_data, 0, sizeof(response_data));
> +	rte_tel_data_start_array(&response_data, RTE_TEL_INT_VAL);
> +	for (i = 0; i < 5; i++)
> +		rte_tel_data_add_array_int(&response_data, i);
> +	return TEST_OUTPUT("{\"/test\":[0,1,2,3,4]}");
> +}
> +
> +static int
> +test_case_add_dict_int(void)
> +{
> +	int i = 0;
> +	char name_of_value[8];
> +
> +	memset(&response_data, 0, sizeof(response_data));
> +	rte_tel_data_start_dict(&response_data);
> +
> +	for (i = 0; i < 5; i++) {
> +		sprintf(name_of_value, "dict_%d", i);
> +		rte_tel_data_add_dict_int(&response_data, name_of_value, i);
> +	}
> +
> +	return TEST_OUTPUT("{\"/test\":{\"dict_0\":0,\"dict_1\":1,\"dict_2\":2,\"dict_3\":3,\"dict_4\":4}}");
> +}
> +
> +static int
> +test_case_array_string(void)
> +{
> +	memset(&response_data, 0, sizeof(response_data));
> +	rte_tel_data_start_array(&response_data, RTE_TEL_STRING_VAL);
> +	rte_tel_data_add_array_string(&response_data, "aaaa");
> +	rte_tel_data_add_array_string(&response_data, "bbbb");
> +	rte_tel_data_add_array_string(&response_data, "cccc");
> +	rte_tel_data_add_array_string(&response_data, "dddd");
> +	rte_tel_data_add_array_string(&response_data, "eeee");
> +
> +	return TEST_OUTPUT("{\"/test\":[\"aaaa\",\"bbbb\",\"cccc\",\"dddd\",\"eeee\"]}");
> +}
> +
> +static int
> +test_case_add_dict_string(void)
> +{
> +	memset(&response_data, 0, sizeof(response_data));
> +	rte_tel_data_start_dict(&response_data);
> +
> +	rte_tel_data_add_dict_string(&response_data, "dict_0", "aaaa");
> +	rte_tel_data_add_dict_string(&response_data, "dict_1", "bbbb");
> +	rte_tel_data_add_dict_string(&response_data, "dict_2", "cccc");
> +	rte_tel_data_add_dict_string(&response_data, "dict_3", "dddd");
> +
> +	return TEST_OUTPUT("{\"/test\":{\"dict_0\":\"aaaa\",\"dict_1\":\"bbbb\",\"dict_2\":\"cccc\",\"dict_3\":\"dddd\"}}");
> +}

Although these are single strings, they are not user output, and so you can
split them across lines to limit line length.

> +
> +
> +static int
> +test_dict_with_array_string_values(void)
> +{
> +	struct rte_tel_data *child_data = rte_tel_data_alloc();
> +	rte_tel_data_start_array(child_data, RTE_TEL_STRING_VAL);
> +
> +	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
> +	rte_tel_data_start_array(child_data2, RTE_TEL_STRING_VAL);
> +
> +	memset(&response_data, 0, sizeof(response_data));
> +	rte_tel_data_start_dict(&response_data);
> +
> +	rte_tel_data_add_array_string(child_data, "aaaa");
> +	rte_tel_data_add_array_string(child_data2, "bbbb");
> +
> +	rte_tel_data_add_dict_container(&response_data, "dict_0",
> +	 child_data, 0);
> +	rte_tel_data_add_dict_container(&response_data, "dict_1",
> +	 child_data2, 0);
> +
> +	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[\"aaaa\"],\"dict_1\":[\"bbbb\"]}}");
> +}
> +
> +static int
> +test_array_with_array_string_values(void)
> +{
> +	struct rte_tel_data *child_data = rte_tel_data_alloc();
> +	rte_tel_data_start_array(child_data, RTE_TEL_STRING_VAL);
> +
> +	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
> +	rte_tel_data_start_array(child_data2, RTE_TEL_STRING_VAL);
> +
> +	memset(&response_data, 0, sizeof(response_data));
> +	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
> +
> +	rte_tel_data_add_array_string(child_data, "aaaa");
> +	rte_tel_data_add_array_string(child_data2, "bbbb");
> +
> +	rte_tel_data_add_array_container(&response_data, child_data, 0);
> +	rte_tel_data_add_array_container(&response_data, child_data2, 0);
> +
> +	return TEST_OUTPUT("{\"/test\":[[\"aaaa\"],[\"bbbb\"]]}");
> +}
> +
> +static int
> +test_case_array_u64(void)
> +{
> +	int i;
> +	memset(&response_data, 0, sizeof(response_data));
> +	rte_tel_data_start_array(&response_data, RTE_TEL_U64_VAL);
> +	for (i = 0; i < 5; i++)
> +		rte_tel_data_add_array_u64(&response_data, i);
> +	return TEST_OUTPUT("{\"/test\":[0,1,2,3,4]}");
> +}
> +
> +static int
> +test_case_add_dict_u64(void)
> +{
> +	int i = 0;
> +	char name_of_value[8];
> +
> +	memset(&response_data, 0, sizeof(response_data));
> +	rte_tel_data_start_dict(&response_data);
> +
> +	for (i = 0; i < 5; i++) {
> +		sprintf(name_of_value, "dict_%d", i);
> +		rte_tel_data_add_dict_u64(&response_data, name_of_value, i);
> +	}
> +	return TEST_OUTPUT("{\"/test\":{\"dict_0\":0,\"dict_1\":1,\"dict_2\":2,\"dict_3\":3,\"dict_4\":4}}");
> +}
> +
> +static int
> +test_dict_with_array_u64_values(void)
> +{
> +	int i;
> +
> +	struct rte_tel_data *child_data = rte_tel_data_alloc();
> +	rte_tel_data_start_array(child_data, RTE_TEL_U64_VAL);
> +
> +	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
> +	rte_tel_data_start_array(child_data2, RTE_TEL_U64_VAL);
> +
> +	memset(&response_data, 0, sizeof(response_data));
> +	rte_tel_data_start_dict(&response_data);
> +
> +	for (i = 0; i < 10; i++) {
> +		rte_tel_data_add_array_u64(child_data, i);
> +		rte_tel_data_add_array_u64(child_data2, i);
> +	}
> +
> +	rte_tel_data_add_dict_container(&response_data, "dict_0",
> +	 child_data, 0);
> +	rte_tel_data_add_dict_container(&response_data, "dict_1",
> +	 child_data2, 0);
> +
> +	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[0,1,2,3,4,5,6,7,8,9],\"dict_1\":[0,1,2,3,4,5,6,7,8,9]}}");
> +}
> +
> +static int
> +test_array_with_array_u64_values(void)
> +{
> +	int i;
> +
> +	struct rte_tel_data *child_data = rte_tel_data_alloc();
> +	rte_tel_data_start_array(child_data, RTE_TEL_U64_VAL);
> +
> +	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
> +	rte_tel_data_start_array(child_data2, RTE_TEL_U64_VAL);
> +
> +	memset(&response_data, 0, sizeof(response_data));
> +	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
> +
> +	for (i = 0; i < 5; i++) {
> +		rte_tel_data_add_array_u64(child_data, i);
> +		rte_tel_data_add_array_u64(child_data2, i);
> +	}
> +	rte_tel_data_add_array_container(&response_data, child_data, 0);
> +	rte_tel_data_add_array_container(&response_data, child_data2, 0);
> +
> +	return TEST_OUTPUT("{\"/test\":[[0,1,2,3,4],[0,1,2,3,4]]}");
> +}
> +
> +static int
> +connect_to_socket(void)
> +{
> +	char file_pattern[PATH_MAX];
> +	char buf[BUF_SIZE];
> +	glob_t globbuf;
> +	int sock, bytes;
> +	struct sockaddr_un telem_addr;
> +
> +	snprintf(file_pattern, sizeof(file_pattern),
> +			"%s/dpdk_telemetry.%s",	rte_eal_get_runtime_dir(),
> +			TELEMETRY_VERSION);
> +	if (glob(file_pattern, 0, NULL, &globbuf) != 0) {
> +		printf("\n%s: Error finding socket file path: %s\n", __func__,
> +				strerror(errno));
> +		globfree(&globbuf);
> +		return -1;
> +	}
> +

The path you create here should be the exact path that you need, no
globbing should be necessary, which I think should simplify the code a bit.

> +	sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
> +	if (sock < 0) {
> +		printf("\n%s: Error creating socket: %s\n", __func__,
> +				strerror(errno));
> +		return -1;
> +	}
> +	telem_addr.sun_family = AF_UNIX;
> +	strlcpy(telem_addr.sun_path, globbuf.gl_pathv[0],
> +			sizeof(telem_addr.sun_path));
> +	if (connect(sock, (struct sockaddr *) &telem_addr,
> +			sizeof(telem_addr)) < 0)
> +		printf("\n%s: Error connecting to socket: %s\n", __func__,
> +				strerror(errno));
> +
> +	bytes = read(sock, buf, sizeof(buf));
> +	if (bytes < 0) {
> +		printf("%s: Error with socket read - %s\n", __func__,
> +				strerror(errno));
> +		return -1;
> +	}
> +	buf[bytes] = '\0';

Again, watch for overflow when you hit the exact output limit.

> +	printf("\n%s: %s\n", __func__, buf);
> +	globfree(&globbuf);
> +	return sock;
> +}
> +
> +static int
> +test_telemetry_data(void)
> +{
> +	typedef int (*test_case)(void);
> +	unsigned int i = 0;
> +
> +	sock = connect_to_socket();
> +	if (sock <= 0)
> +		return -1;
> +
> +	test_case test_cases[] = {test_case_array_string,
> +			test_case_array_int, test_case_array_u64,
> +			test_case_add_dict_int, test_case_add_dict_u64,
> +			test_case_add_dict_string,
> +			test_dict_with_array_int_values,
> +			test_dict_with_array_u64_values,
> +			test_dict_with_array_string_values,
> +			test_array_with_array_int_values,
> +			test_array_with_array_u64_values,
> +			test_array_with_array_string_values };
> +
> +	rte_telemetry_register_cmd(REQUEST_CMD, test_cb, "Test");
> +	for (i = 0; i < RTE_DIM(test_cases); i++) {
> +		if (test_cases[i]() != 0) {
> +			close(sock);
> +			return -1;
> +		}
> +	}

Thanks, this is much more readable than the previous version.

> +	close(sock);
> +	return 0;
> +}
> +
> +REGISTER_TEST_COMMAND(telemetry_data_autotest, test_telemetry_data);
> -- 
> 2.17.1
> 

^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v7 0/3] add basic ethdev stats with data object recursion
  2020-06-12 10:53 [dpdk-dev] [RFC 0/2] add basic ethdev stats with data object recursion Ciara Power
                   ` (6 preceding siblings ...)
  2020-07-20 11:19 ` [dpdk-dev] [PATCH v6 0/3] add basic ethdev stats with data object recursion Ciara Power
@ 2020-07-20 14:04 ` Ciara Power
  2020-07-20 14:04   ` [dpdk-dev] [PATCH v7 1/3] telemetry: support array values in data objects Ciara Power
                     ` (2 more replies)
  2020-08-21 12:51 ` [dpdk-dev] [PATCH v8 0/3] add basic ethdev stats with data object recursion Ciara Power
  2020-09-23 11:12 ` [dpdk-dev] [PATCH v9 0/3] add basic ethdev stats with data object recursion Ciara Power
  9 siblings, 3 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-20 14:04 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, bruce.richardson, Ciara Power

v7:
  - Simplified connecting to socket by removing use of glob.
  - Fixed buffer overflow issue when reading from socket.
  - Split expected response strings over multiple lines.
v6:
  - Fixed FreeBSD build failure for unit tests.
  - Added comments and expanded commit log.
  - Add loop to call test cases stored in a list.
v5: Added unit tests for telemetry data to JSON conversion.
v4: Added missing param description to Doxygen comment.
v3:
  - Modified commit logs.
  - Changed function names to reference container.
  - Modified Doxygen comments to reference container.
v2:
  - Added support for arrays to have container values.
  - Added support for int and string arrays within dict/array.
  - Added APIs for internal container memory management.

This patchset adds support for basic ethdev statistics in Telemetry.
To do this, recursive data object support is needed to report the queue
statistics in a list. With this patch, an array or dictionary supports
uint64_t, int or string array type values, which is used for the ethdev
queue stats.

Ciara Power (2):
  telemetry: support array values in data objects
  ethdev: add common stats for telemetry

Louise Kilheeney (1):
  test/test_telemetry_data: add unit tests for data to JSON

 app/test/Makefile                             |   1 +
 app/test/meson.build                          |   5 +-
 app/test/test_telemetry_data.c                | 369 ++++++++++++++++++
 lib/librte_ethdev/rte_ethdev.c                |  53 +++
 lib/librte_telemetry/rte_telemetry.h          |  70 ++++
 .../rte_telemetry_version.map                 |   4 +
 lib/librte_telemetry/telemetry.c              |  56 +++
 lib/librte_telemetry/telemetry_data.c         |  51 +++
 lib/librte_telemetry/telemetry_data.h         |   7 +
 lib/librte_telemetry/telemetry_json.h         |  33 ++
 10 files changed, 647 insertions(+), 2 deletions(-)
 create mode 100644 app/test/test_telemetry_data.c

-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v7 1/3] telemetry: support array values in data objects
  2020-07-20 14:04 ` [dpdk-dev] [PATCH v7 0/3] add basic ethdev stats with data object recursion Ciara Power
@ 2020-07-20 14:04   ` Ciara Power
  2020-07-20 14:04   ` [dpdk-dev] [PATCH v7 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
  2020-07-20 14:04   ` [dpdk-dev] [PATCH v7 3/3] ethdev: add common stats for telemetry Ciara Power
  2 siblings, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-20 14:04 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, bruce.richardson, Ciara Power

Arrays of type uint64_t/int/string can now be included within an array
or dict. One level of embedded containers is supported. This is
necessary to allow for instances such as the ethdev queue stats to be
reported as a list of uint64_t values, rather than having multiple dict
entries with one uint64_t value for each queue stat.

The memory management APIs provided by telemetry simplify the memory
allocation/free aspect of the embedded container. The rte_tel_data_alloc
function is called in the library/app callback to return a pointer to a
container that has been allocated memory. When adding this container
to an array/dict, a parameter is passed to indicate if the memory
should be freed by telemetry after use. This will allow reuse of the
allocated memory if the library/app wishes to do so.

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

---
v4: Added missing param description to Doxygen comment.
v3:
  - Modified commit log.
  - Changed function name to reference container.
  - Modified Doxygen comments to reference container.
v2:
  - Added support for arrays to have container values.
  - Added support for int and string arrays within dict/array.
  - Added APIs for internal container memory management.
---
 lib/librte_telemetry/rte_telemetry.h          | 70 +++++++++++++++++++
 .../rte_telemetry_version.map                 |  4 ++
 lib/librte_telemetry/telemetry.c              | 56 +++++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 51 ++++++++++++++
 lib/librte_telemetry/telemetry_data.h         |  7 ++
 lib/librte_telemetry/telemetry_json.h         | 33 +++++++++
 6 files changed, 221 insertions(+)

diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
index d13010b8f..c16541226 100644
--- a/lib/librte_telemetry/rte_telemetry.h
+++ b/lib/librte_telemetry/rte_telemetry.h
@@ -46,6 +46,7 @@ enum rte_tel_value_type {
 	RTE_TEL_STRING_VAL, /** a string value */
 	RTE_TEL_INT_VAL,    /** a signed 32-bit int value */
 	RTE_TEL_U64_VAL,    /** an unsigned 64-bit int value */
+	RTE_TEL_CONTAINER, /** a container struct */
 };
 
 /**
@@ -136,6 +137,28 @@ __rte_experimental
 int
 rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x);
 
+/**
+ * Add a container to an array. A container is an existing telemetry data
+ * array. The array the container is to be added to must have been started by
+ * rte_tel_data_start_array() with RTE_TEL_CONTAINER as the type parameter.
+ * The container type must be an array of type uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param val
+ *   The pointer to the container to be stored in the array.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep);
+
 /**
  * Add a string value to a dictionary.
  * The dict must have been started by rte_tel_data_start_dict().
@@ -190,6 +213,30 @@ int
 rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 		const char *name, uint64_t val);
 
+/**
+ * Add a container to a dictionary. A container is an existing telemetry data
+ * array. The dict the container is to be added to must have been started by
+ * rte_tel_data_start_dict(). The container must be an array of type
+ * uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param name
+ *   The name the value is to be stored under in the dict.
+ * @param val
+ *   The pointer to the container to be stored in the dict.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep);
+
 /**
  * This telemetry callback is used when registering a telemetry command.
  * It handles getting and formatting information to be returned to telemetry
@@ -264,4 +311,27 @@ int
 rte_telemetry_init(const char *runtime_dir, rte_cpuset_t *cpuset,
 		const char **err_str);
 
+/**
+ * Get a pointer to a container with memory allocated. The container is to be
+ * used embedded within an existing telemetry dict/array.
+ *
+ * @return
+ *  Pointer to a container.
+ */
+__rte_experimental
+struct rte_tel_data *
+rte_tel_data_alloc(void);
+
+/**
+ * @internal
+ * Free a container that has memory allocated.
+ *
+ * @param data
+ *  Pointer to container.
+ *.
+ */
+__rte_experimental
+void
+rte_tel_data_free(struct rte_tel_data *data);
+
 #endif
diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map
index 86433c21d..d1dbf8d58 100644
--- a/lib/librte_telemetry/rte_telemetry_version.map
+++ b/lib/librte_telemetry/rte_telemetry_version.map
@@ -1,12 +1,16 @@
 EXPERIMENTAL {
 	global:
 
+	rte_tel_data_add_array_container;
 	rte_tel_data_add_array_int;
 	rte_tel_data_add_array_string;
 	rte_tel_data_add_array_u64;
+	rte_tel_data_add_dict_container;
 	rte_tel_data_add_dict_int;
 	rte_tel_data_add_dict_string;
 	rte_tel_data_add_dict_u64;
+	rte_tel_data_alloc;
+	rte_tel_data_free;
 	rte_tel_data_start_array;
 	rte_tel_data_start_dict;
 	rte_tel_data_string;
diff --git a/lib/librte_telemetry/telemetry.c b/lib/librte_telemetry/telemetry.c
index 025228273..4469885c3 100644
--- a/lib/librte_telemetry/telemetry.c
+++ b/lib/librte_telemetry/telemetry.c
@@ -123,6 +123,35 @@ command_help(const char *cmd __rte_unused, const char *params,
 	return 0;
 }
 
+static int
+container_to_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len)
+{
+	size_t used = 0;
+	unsigned int i;
+
+	if (d->type != RTE_TEL_ARRAY_U64 && d->type != RTE_TEL_ARRAY_INT
+			&& d->type != RTE_TEL_ARRAY_STRING)
+		return snprintf(out_buf, buf_len, "null");
+
+	used = rte_tel_json_empty_array(out_buf, buf_len, 0);
+	if (d->type == RTE_TEL_ARRAY_U64)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_u64(out_buf,
+				buf_len, used,
+				d->data.array[i].u64val);
+	if (d->type == RTE_TEL_ARRAY_INT)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_int(out_buf,
+				buf_len, used,
+				d->data.array[i].ival);
+	if (d->type == RTE_TEL_ARRAY_STRING)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_string(out_buf,
+				buf_len, used,
+				d->data.array[i].sval);
+	return used;
+}
+
 static void
 output_json(const char *cmd, const struct rte_tel_data *d, int s)
 {
@@ -169,6 +198,20 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 						buf_len, used,
 						v->name, v->value.u64val);
 				break;
+			case RTE_TEL_CONTAINER:
+			{
+				char temp[buf_len];
+				const struct container *cont =
+						&v->value.container;
+				if (container_to_json(cont->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_obj_json(
+							cb_data_buf,
+							buf_len, used,
+							v->name, temp);
+				if (!cont->keep)
+					rte_tel_data_free(cont->data);
+			}
 			}
 		}
 		used += prefix_used;
@@ -177,6 +220,7 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 	case RTE_TEL_ARRAY_STRING:
 	case RTE_TEL_ARRAY_INT:
 	case RTE_TEL_ARRAY_U64:
+	case RTE_TEL_ARRAY_CONTAINER:
 		prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
 				MAX_CMD_LEN, cmd);
 		cb_data_buf = &out_buf[prefix_used];
@@ -197,6 +241,18 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 				used = rte_tel_json_add_array_u64(cb_data_buf,
 						buf_len, used,
 						d->data.array[i].u64val);
+			else if (d->type == RTE_TEL_ARRAY_CONTAINER) {
+				char temp[buf_len];
+				const struct container *rec_data =
+						&d->data.array[i].container;
+				if (container_to_json(rec_data->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_array_json(
+							cb_data_buf,
+							buf_len, used, temp);
+				if (!rec_data->keep)
+					rte_tel_data_free(rec_data->data);
+			}
 		used += prefix_used;
 		used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
 		break;
diff --git a/lib/librte_telemetry/telemetry_data.c b/lib/librte_telemetry/telemetry_data.c
index f424bbd48..77b0fe09a 100644
--- a/lib/librte_telemetry/telemetry_data.c
+++ b/lib/librte_telemetry/telemetry_data.c
@@ -14,6 +14,7 @@ rte_tel_data_start_array(struct rte_tel_data *d, enum rte_tel_value_type type)
 			RTE_TEL_ARRAY_STRING, /* RTE_TEL_STRING_VAL = 0 */
 			RTE_TEL_ARRAY_INT,    /* RTE_TEL_INT_VAL = 1 */
 			RTE_TEL_ARRAY_U64,    /* RTE_TEL_u64_VAL = 2 */
+			RTE_TEL_ARRAY_CONTAINER, /* RTE_TEL_CONTAINER = 3 */
 	};
 	d->type = array_types[type];
 	d->data_len = 0;
@@ -74,6 +75,23 @@ rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x)
 	return 0;
 }
 
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep)
+{
+	if (d->type != RTE_TEL_ARRAY_CONTAINER ||
+			(val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_ARRAY_ENTRIES)
+		return -ENOSPC;
+
+	d->data.array[d->data_len].container.data = val;
+	d->data.array[d->data_len++].container.keep = !!keep;
+	return 0;
+}
+
 int
 rte_tel_data_add_dict_string(struct rte_tel_data *d, const char *name,
 		const char *val)
@@ -128,3 +146,36 @@ rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
 	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
 }
+
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep)
+{
+	struct tel_dict_entry *e = &d->data.dict[d->data_len];
+
+	if (d->type != RTE_TEL_DICT || (val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES)
+		return -ENOSPC;
+
+	d->data_len++;
+	e->type = RTE_TEL_CONTAINER;
+	e->value.container.data = val;
+	e->value.container.keep = !!keep;
+	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
+	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
+}
+
+struct rte_tel_data *
+rte_tel_data_alloc(void)
+{
+	return malloc(sizeof(struct rte_tel_data));
+}
+
+void
+rte_tel_data_free(struct rte_tel_data *data)
+{
+	free(data);
+}
diff --git a/lib/librte_telemetry/telemetry_data.h b/lib/librte_telemetry/telemetry_data.h
index ff3a371a3..adb84a09f 100644
--- a/lib/librte_telemetry/telemetry_data.h
+++ b/lib/librte_telemetry/telemetry_data.h
@@ -15,6 +15,12 @@ enum tel_container_types {
 	RTE_TEL_ARRAY_STRING, /** array of string values only */
 	RTE_TEL_ARRAY_INT,    /** array of signed, 32-bit int values */
 	RTE_TEL_ARRAY_U64,    /** array of unsigned 64-bit int values */
+	RTE_TEL_ARRAY_CONTAINER, /** array of container structs */
+};
+
+struct container {
+	struct rte_tel_data *data;
+	int keep;
 };
 
 /* each type here must have an equivalent enum in the value types enum in
@@ -25,6 +31,7 @@ union tel_value {
 	char sval[RTE_TEL_MAX_STRING_LEN];
 	int ival;
 	uint64_t u64val;
+	struct container container;
 };
 
 struct tel_dict_entry {
diff --git a/lib/librte_telemetry/telemetry_json.h b/lib/librte_telemetry/telemetry_json.h
index a2ce4899e..ad270b9b3 100644
--- a/lib/librte_telemetry/telemetry_json.h
+++ b/lib/librte_telemetry/telemetry_json.h
@@ -102,6 +102,22 @@ rte_tel_json_add_array_u64(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/*
+ * Add a new element with raw JSON value to the JSON array stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_array_json(char *buf, const int len, const int used,
+		const char *str)
+{
+	int ret, end = used - 1; /* strip off final delimiter */
+	if (used <= 2) /* assume empty, since minimum is '[]' */
+		return __json_snprintf(buf, len, "[%s]", str);
+
+	ret = __json_snprintf(buf + end, len - end, ",%s]", str);
+	return ret == 0 ? used : end + ret;
+}
+
 /**
  * Add a new element with uint64_t value to the JSON object stored in the
  * provided buffer.
@@ -155,4 +171,21 @@ rte_tel_json_add_obj_str(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/**
+ * Add a new element with raw JSON value to the JSON object stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_obj_json(char *buf, const int len, const int used,
+		const char *name, const char *val)
+{
+	int ret, end = used - 1;
+	if (used <= 2) /* assume empty, since minimum is '{}' */
+		return __json_snprintf(buf, len, "{\"%s\":%s}", name, val);
+
+	ret = __json_snprintf(buf + end, len - end, ",\"%s\":%s}",
+			name, val);
+	return ret == 0 ? used : end + ret;
+}
+
 #endif /*_RTE_TELEMETRY_JSON_H_*/
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v7 2/3] test/test_telemetry_data: add unit tests for data to JSON
  2020-07-20 14:04 ` [dpdk-dev] [PATCH v7 0/3] add basic ethdev stats with data object recursion Ciara Power
  2020-07-20 14:04   ` [dpdk-dev] [PATCH v7 1/3] telemetry: support array values in data objects Ciara Power
@ 2020-07-20 14:04   ` Ciara Power
  2020-07-20 14:32     ` Bruce Richardson
  2020-07-20 14:04   ` [dpdk-dev] [PATCH v7 3/3] ethdev: add common stats for telemetry Ciara Power
  2 siblings, 1 reply; 44+ messages in thread
From: Ciara Power @ 2020-07-20 14:04 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, bruce.richardson, Louise Kilheeney, Ciara Power

From: Louise Kilheeney <louise.kilheeney@intel.com>

This patch adds tests for verifying telemetry data structures are
converted to JSON as expected. Both flat and recursive data structures
are tested, for all possible value types.

The app connects to the telemetry socket as a client, and registers one
command with a corresponding callback function. Each time the callback
function is called, it copies a global data variable to the data pointer
passed in by telemetry.
When a test case is run, the test case function builds up the global
data variable with the relevant data types, and the expected json string
output which should be generated from that. The 'test_output()' function
is used to trigger the callback and ensure the actual output matches
that expected.

Signed-off-by: Louise Kilheeney <louise.kilheeney@intel.com>
Signed-off-by: Ciara Power <ciara.power@intel.com>

---
v7:
  - Simplified connecting to socket by removing use of glob.
  - Fixed buffer overflow issue when reading from socket.
  - Split expected response strings over multiple lines.
v6:
  - Fixed FreeBSD build error.
  - Added comments and expanded commit log.
  - Add loop to call test cases stored in a list.
---
 app/test/Makefile              |   1 +
 app/test/meson.build           |   5 +-
 app/test/test_telemetry_data.c | 369 +++++++++++++++++++++++++++++++++
 3 files changed, 373 insertions(+), 2 deletions(-)
 create mode 100644 app/test/test_telemetry_data.c

diff --git a/app/test/Makefile b/app/test/Makefile
index f4065271e..1cb64089c 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -145,6 +145,7 @@ SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6.c
 SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6_perf.c
 
 SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += test_telemetry_json.c
+SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += test_telemetry_data.c
 
 SRCS-y += test_debug.c
 SRCS-y += test_errno.c
diff --git a/app/test/meson.build b/app/test/meson.build
index 786a21397..4a72fe5b6 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -170,6 +170,7 @@ test_deps = ['acl',
 	'ring',
 	'security',
 	'stack',
+	'telemetry',
 	'timer'
 ]
 
@@ -345,8 +346,8 @@ if dpdk_conf.has('RTE_LIBRTE_SKELETON_EVENTDEV_PMD')
 	test_deps += 'pmd_skeleton_event'
 endif
 if dpdk_conf.has('RTE_LIBRTE_TELEMETRY')
-	test_sources += 'test_telemetry_json.c'
-	fast_tests += [['telemetry_json_autotest', true]]
+	test_sources += ['test_telemetry_json.c', 'test_telemetry_data.c']
+	fast_tests += [['telemetry_json_autotest', true], ['telemetry_data_autotest', true]]
 endif
 
 # The following linkages of drivers are required because
diff --git a/app/test/test_telemetry_data.c b/app/test/test_telemetry_data.c
new file mode 100644
index 000000000..7a31e68a7
--- /dev/null
+++ b/app/test/test_telemetry_data.c
@@ -0,0 +1,369 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2020 Intel Corporation
+ */
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <rte_eal.h>
+#include <rte_common.h>
+#include <rte_telemetry.h>
+#include <rte_string_fns.h>
+
+#include "test.h"
+#include "telemetry_data.h"
+
+#define TELEMETRY_VERSION "v2"
+#define REQUEST_CMD "/test"
+#define BUF_SIZE 1024
+#define TEST_OUTPUT(exp) test_output(__func__, exp)
+
+static struct rte_tel_data response_data;
+static int sock;
+
+/*
+ * This function is the callback registered with Telemetry to be used when
+ * the /test command is requested. This callback returns the global data built
+ * up by the individual test cases.
+ */
+static int
+test_cb(const char *cmd __rte_unused, const char *params __rte_unused,
+		struct rte_tel_data *d)
+{
+	*d = response_data;
+	return 0;
+}
+
+/*
+ * This function is called by each test case function. It communicates with
+ * the telemetry socket by requesting the /test command, and reading the
+ * response. The expected response is passed in by the test case function,
+ * and is compared to the actual response received from Telemetry.
+ */
+static int
+test_output(const char *func_name, const char *expected)
+{
+	int bytes;
+	char buf[BUF_SIZE * 16];
+	if (write(sock, REQUEST_CMD, strlen(REQUEST_CMD)) < 0) {
+		printf("%s: Error with socket write - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	bytes = read(sock, buf, sizeof(buf) - 1);
+	if (bytes < 0) {
+		printf("%s: Error with socket read - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	buf[bytes] = '\0';
+	printf("%s: buf = '%s', expected = '%s'\n", func_name, buf, expected);
+	return strncmp(expected, buf, sizeof(buf));
+}
+
+static int
+test_dict_with_array_int_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_INT_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_INT_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_int(child_data, i);
+		rte_tel_data_add_array_int(child_data2, i);
+	}
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[0,1,2,3,4],"
+			"\"dict_1\":[0,1,2,3,4]}}");
+}
+
+static int
+test_array_with_array_int_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_INT_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_INT_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_int(child_data, i);
+		rte_tel_data_add_array_int(child_data2, i);
+	}
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[0,1,2,3,4],[0,1,2,3,4]]}");
+}
+
+static int
+test_case_array_int(void)
+{
+	int i;
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_INT_VAL);
+	for (i = 0; i < 5; i++)
+		rte_tel_data_add_array_int(&response_data, i);
+	return TEST_OUTPUT("{\"/test\":[0,1,2,3,4]}");
+}
+
+static int
+test_case_add_dict_int(void)
+{
+	int i = 0;
+	char name_of_value[8];
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		sprintf(name_of_value, "dict_%d", i);
+		rte_tel_data_add_dict_int(&response_data, name_of_value, i);
+	}
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":0,\"dict_1\":1,\"dict_2\":2,"
+			"\"dict_3\":3,\"dict_4\":4}}");
+}
+
+static int
+test_case_array_string(void)
+{
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_STRING_VAL);
+	rte_tel_data_add_array_string(&response_data, "aaaa");
+	rte_tel_data_add_array_string(&response_data, "bbbb");
+	rte_tel_data_add_array_string(&response_data, "cccc");
+	rte_tel_data_add_array_string(&response_data, "dddd");
+	rte_tel_data_add_array_string(&response_data, "eeee");
+
+	return TEST_OUTPUT("{\"/test\":[\"aaaa\",\"bbbb\",\"cccc\",\"dddd\","
+			"\"eeee\"]}");
+}
+
+static int
+test_case_add_dict_string(void)
+{
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	rte_tel_data_add_dict_string(&response_data, "dict_0", "aaaa");
+	rte_tel_data_add_dict_string(&response_data, "dict_1", "bbbb");
+	rte_tel_data_add_dict_string(&response_data, "dict_2", "cccc");
+	rte_tel_data_add_dict_string(&response_data, "dict_3", "dddd");
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":\"aaaa\",\"dict_1\":"
+			"\"bbbb\",\"dict_2\":\"cccc\",\"dict_3\":\"dddd\"}}");
+}
+
+
+static int
+test_dict_with_array_string_values(void)
+{
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_STRING_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_STRING_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	rte_tel_data_add_array_string(child_data, "aaaa");
+	rte_tel_data_add_array_string(child_data2, "bbbb");
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[\"aaaa\"],\"dict_1\":"
+			"[\"bbbb\"]}}");
+}
+
+static int
+test_array_with_array_string_values(void)
+{
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_STRING_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_STRING_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	rte_tel_data_add_array_string(child_data, "aaaa");
+	rte_tel_data_add_array_string(child_data2, "bbbb");
+
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[\"aaaa\"],[\"bbbb\"]]}");
+}
+
+static int
+test_case_array_u64(void)
+{
+	int i;
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_U64_VAL);
+	for (i = 0; i < 5; i++)
+		rte_tel_data_add_array_u64(&response_data, i);
+	return TEST_OUTPUT("{\"/test\":[0,1,2,3,4]}");
+}
+
+static int
+test_case_add_dict_u64(void)
+{
+	int i = 0;
+	char name_of_value[8];
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		sprintf(name_of_value, "dict_%d", i);
+		rte_tel_data_add_dict_u64(&response_data, name_of_value, i);
+	}
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":0,\"dict_1\":1,\"dict_2\":2,"
+			"\"dict_3\":3,\"dict_4\":4}}");
+}
+
+static int
+test_dict_with_array_u64_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_U64_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_U64_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 10; i++) {
+		rte_tel_data_add_array_u64(child_data, i);
+		rte_tel_data_add_array_u64(child_data2, i);
+	}
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[0,1,2,3,4,5,6,7,8,9],"
+			"\"dict_1\":[0,1,2,3,4,5,6,7,8,9]}}");
+}
+
+static int
+test_array_with_array_u64_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_U64_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_U64_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_u64(child_data, i);
+		rte_tel_data_add_array_u64(child_data2, i);
+	}
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[0,1,2,3,4],[0,1,2,3,4]]}");
+}
+
+static int
+connect_to_socket(void)
+{
+	char buf[BUF_SIZE];
+	int sock, bytes;
+	struct sockaddr_un telem_addr;
+
+	sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+	if (sock < 0) {
+		printf("\n%s: Error creating socket: %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	telem_addr.sun_family = AF_UNIX;
+	snprintf(telem_addr.sun_path, sizeof(telem_addr.sun_path),
+			"%s/dpdk_telemetry.%s",	rte_eal_get_runtime_dir(),
+			TELEMETRY_VERSION);
+	if (connect(sock, (struct sockaddr *) &telem_addr,
+			sizeof(telem_addr)) < 0)
+		printf("\n%s: Error connecting to socket: %s\n", __func__,
+				strerror(errno));
+
+	bytes = read(sock, buf, sizeof(buf) - 1);
+	if (bytes < 0) {
+		printf("%s: Error with socket read - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	buf[bytes] = '\0';
+	printf("\n%s: %s\n", __func__, buf);
+	return sock;
+}
+
+static int
+test_telemetry_data(void)
+{
+	typedef int (*test_case)(void);
+	unsigned int i = 0;
+
+	sock = connect_to_socket();
+	if (sock <= 0)
+		return -1;
+
+	test_case test_cases[] = {test_case_array_string,
+			test_case_array_int, test_case_array_u64,
+			test_case_add_dict_int, test_case_add_dict_u64,
+			test_case_add_dict_string,
+			test_dict_with_array_int_values,
+			test_dict_with_array_u64_values,
+			test_dict_with_array_string_values,
+			test_array_with_array_int_values,
+			test_array_with_array_u64_values,
+			test_array_with_array_string_values };
+
+	rte_telemetry_register_cmd(REQUEST_CMD, test_cb, "Test");
+	for (i = 0; i < RTE_DIM(test_cases); i++) {
+		if (test_cases[i]() != 0) {
+			close(sock);
+			return -1;
+		}
+	}
+	close(sock);
+	return 0;
+}
+
+REGISTER_TEST_COMMAND(telemetry_data_autotest, test_telemetry_data);
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v7 3/3] ethdev: add common stats for telemetry
  2020-07-20 14:04 ` [dpdk-dev] [PATCH v7 0/3] add basic ethdev stats with data object recursion Ciara Power
  2020-07-20 14:04   ` [dpdk-dev] [PATCH v7 1/3] telemetry: support array values in data objects Ciara Power
  2020-07-20 14:04   ` [dpdk-dev] [PATCH v7 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
@ 2020-07-20 14:04   ` Ciara Power
  2 siblings, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-07-20 14:04 UTC (permalink / raw)
  To: kevin.laatz, thomas, ferruh.yigit, arybchenko
  Cc: dev, keith.wiles, bruce.richardson, Ciara Power

The ethdev library now registers a telemetry command for common ethdev
statistics.

An example usage is shown below:

Connecting to /var/run/dpdk/rte/dpdk_telemetry.v2
{"version": "DPDK 20.08.0-rc1", "pid": 14119, "max_output_len": 16384}
--> /ethdev/stats,0
{"/ethdev/stats": {"ipackets": 0, "opackets": 0, "ibytes": 0, "obytes": \
    0, "imissed": 0, "ierrors": 0, "oerrors": 0, "rx_nombuf": 0, \
    "q_ipackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_opackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_ibytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_obytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_errors": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}}

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

---
v3:
  - Updated help text and commit subject line.
v2:
  - Updated to use memory management APIs.
---
 lib/librte_ethdev/rte_ethdev.c | 53 ++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
index 7858ad5f1..8ee14b1b5 100644
--- a/lib/librte_ethdev/rte_ethdev.c
+++ b/lib/librte_ethdev/rte_ethdev.c
@@ -5275,6 +5275,57 @@ handle_port_list(const char *cmd __rte_unused,
 	return 0;
 }
 
+static void
+add_port_queue_stats(struct rte_tel_data *d, uint64_t *q_stats,
+		const char *stat_name)
+{
+	int q;
+	struct rte_tel_data *q_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(q_data, RTE_TEL_U64_VAL);
+	for (q = 0; q < RTE_ETHDEV_QUEUE_STAT_CNTRS; q++)
+		rte_tel_data_add_array_u64(q_data, q_stats[q]);
+	rte_tel_data_add_dict_container(d, stat_name, q_data, 0);
+}
+
+#define ADD_DICT_STAT(stats, s) rte_tel_data_add_dict_u64(d, #s, stats.s)
+
+static int
+handle_port_stats(const char *cmd __rte_unused,
+		const char *params,
+		struct rte_tel_data *d)
+{
+	struct rte_eth_stats stats;
+	int port_id, ret;
+
+	if (params == NULL || strlen(params) == 0 || !isdigit(*params))
+		return -1;
+
+	port_id = atoi(params);
+	if (!rte_eth_dev_is_valid_port(port_id))
+		return -1;
+
+	ret = rte_eth_stats_get(port_id, &stats);
+	if (ret < 0)
+		return -1;
+
+	rte_tel_data_start_dict(d);
+	ADD_DICT_STAT(stats, ipackets);
+	ADD_DICT_STAT(stats, opackets);
+	ADD_DICT_STAT(stats, ibytes);
+	ADD_DICT_STAT(stats, obytes);
+	ADD_DICT_STAT(stats, imissed);
+	ADD_DICT_STAT(stats, ierrors);
+	ADD_DICT_STAT(stats, oerrors);
+	ADD_DICT_STAT(stats, rx_nombuf);
+	add_port_queue_stats(d, stats.q_ipackets, "q_ipackets");
+	add_port_queue_stats(d, stats.q_opackets, "q_opackets");
+	add_port_queue_stats(d, stats.q_ibytes, "q_ibytes");
+	add_port_queue_stats(d, stats.q_obytes, "q_obytes");
+	add_port_queue_stats(d, stats.q_errors, "q_errors");
+
+	return 0;
+}
+
 static int
 handle_port_xstats(const char *cmd __rte_unused,
 		const char *params,
@@ -5361,6 +5412,8 @@ RTE_INIT(ethdev_init_telemetry)
 {
 	rte_telemetry_register_cmd("/ethdev/list", handle_port_list,
 			"Returns list of available ethdev ports. Takes no parameters");
+	rte_telemetry_register_cmd("/ethdev/stats", handle_port_stats,
+			"Returns the common stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/xstats", handle_port_xstats,
 			"Returns the extended stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/link_status",
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [dpdk-dev] [PATCH v7 2/3] test/test_telemetry_data: add unit tests for data to JSON
  2020-07-20 14:04   ` [dpdk-dev] [PATCH v7 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
@ 2020-07-20 14:32     ` Bruce Richardson
  0 siblings, 0 replies; 44+ messages in thread
From: Bruce Richardson @ 2020-07-20 14:32 UTC (permalink / raw)
  To: Ciara Power
  Cc: kevin.laatz, thomas, ferruh.yigit, arybchenko, dev, keith.wiles,
	Louise Kilheeney

On Mon, Jul 20, 2020 at 03:04:02PM +0100, Ciara Power wrote:
> From: Louise Kilheeney <louise.kilheeney@intel.com>
> 
> This patch adds tests for verifying telemetry data structures are
> converted to JSON as expected. Both flat and recursive data structures
> are tested, for all possible value types.
> 
> The app connects to the telemetry socket as a client, and registers one
> command with a corresponding callback function. Each time the callback
> function is called, it copies a global data variable to the data pointer
> passed in by telemetry.
> When a test case is run, the test case function builds up the global
> data variable with the relevant data types, and the expected json string
> output which should be generated from that. The 'test_output()' function
> is used to trigger the callback and ensure the actual output matches
> that expected.
> 
> Signed-off-by: Louise Kilheeney <louise.kilheeney@intel.com>
> Signed-off-by: Ciara Power <ciara.power@intel.com>
> 
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v8 0/3] add basic ethdev stats with data object recursion
  2020-06-12 10:53 [dpdk-dev] [RFC 0/2] add basic ethdev stats with data object recursion Ciara Power
                   ` (7 preceding siblings ...)
  2020-07-20 14:04 ` [dpdk-dev] [PATCH v7 0/3] add basic ethdev stats with data object recursion Ciara Power
@ 2020-08-21 12:51 ` Ciara Power
  2020-08-21 12:51   ` [dpdk-dev] [PATCH v8 1/3] telemetry: support array values in data objects Ciara Power
                     ` (2 more replies)
  2020-09-23 11:12 ` [dpdk-dev] [PATCH v9 0/3] add basic ethdev stats with data object recursion Ciara Power
  9 siblings, 3 replies; 44+ messages in thread
From: Ciara Power @ 2020-08-21 12:51 UTC (permalink / raw)
  To: dev; +Cc: keith.wiles, bruce.richardson, Ciara Power

v8: Rebased onto main.
v7:
  - Simplified connecting to socket by removing use of glob.
  - Fixed buffer overflow issue when reading from socket.
  - Split expected response strings over multiple lines.
v6:
  - Fixed FreeBSD build failure for unit tests.
  - Added comments and expanded commit log.
  - Add loop to call test cases stored in a list.
v5: Added unit tests for telemetry data to JSON conversion.
v4: Added missing param description to Doxygen comment.
v3:
  - Modified commit logs.
  - Changed function names to reference container.
  - Modified Doxygen comments to reference container.
v2:
  - Added support for arrays to have container values.
  - Added support for int and string arrays within dict/array.
  - Added APIs for internal container memory management.

This patchset adds support for basic ethdev statistics in Telemetry.
To do this, recursive data object support is needed to report the queue
statistics in a list. With this patch, an array or dictionary supports
uint64_t, int or string array type values, which is used for the ethdev
queue stats.

Ciara Power (2):
  telemetry: support array values in data objects
  ethdev: add common stats for telemetry

Louise Kilheeney (1):
  test/test_telemetry_data: add unit tests for data to JSON

 app/test/Makefile                             |   1 +
 app/test/meson.build                          |   5 +-
 app/test/test_telemetry_data.c                | 369 ++++++++++++++++++
 lib/librte_ethdev/rte_ethdev.c                |  53 +++
 lib/librte_telemetry/rte_telemetry.h          |  70 ++++
 .../rte_telemetry_version.map                 |   4 +
 lib/librte_telemetry/telemetry.c              |  56 +++
 lib/librte_telemetry/telemetry_data.c         |  51 +++
 lib/librte_telemetry/telemetry_data.h         |   7 +
 lib/librte_telemetry/telemetry_json.h         |  33 ++
 10 files changed, 647 insertions(+), 2 deletions(-)
 create mode 100644 app/test/test_telemetry_data.c

-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v8 1/3] telemetry: support array values in data objects
  2020-08-21 12:51 ` [dpdk-dev] [PATCH v8 0/3] add basic ethdev stats with data object recursion Ciara Power
@ 2020-08-21 12:51   ` Ciara Power
  2020-08-21 12:51   ` [dpdk-dev] [PATCH v8 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
  2020-08-21 12:51   ` [dpdk-dev] [PATCH v8 3/3] ethdev: add common stats for telemetry Ciara Power
  2 siblings, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-08-21 12:51 UTC (permalink / raw)
  To: dev
  Cc: keith.wiles, bruce.richardson, Ciara Power, Kevin Laatz,
	Ray Kinsella, Neil Horman

Arrays of type uint64_t/int/string can now be included within an array
or dict. One level of embedded containers is supported. This is
necessary to allow for instances such as the ethdev queue stats to be
reported as a list of uint64_t values, rather than having multiple dict
entries with one uint64_t value for each queue stat.

The memory management APIs provided by telemetry simplify the memory
allocation/free aspect of the embedded container. The rte_tel_data_alloc
function is called in the library/app callback to return a pointer to a
container that has been allocated memory. When adding this container
to an array/dict, a parameter is passed to indicate if the memory
should be freed by telemetry after use. This will allow reuse of the
allocated memory if the library/app wishes to do so.

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
---
 lib/librte_telemetry/rte_telemetry.h          | 70 +++++++++++++++++++
 .../rte_telemetry_version.map                 |  4 ++
 lib/librte_telemetry/telemetry.c              | 56 +++++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 51 ++++++++++++++
 lib/librte_telemetry/telemetry_data.h         |  7 ++
 lib/librte_telemetry/telemetry_json.h         | 33 +++++++++
 6 files changed, 221 insertions(+)

diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
index d13010b8fb..c165412260 100644
--- a/lib/librte_telemetry/rte_telemetry.h
+++ b/lib/librte_telemetry/rte_telemetry.h
@@ -46,6 +46,7 @@ enum rte_tel_value_type {
 	RTE_TEL_STRING_VAL, /** a string value */
 	RTE_TEL_INT_VAL,    /** a signed 32-bit int value */
 	RTE_TEL_U64_VAL,    /** an unsigned 64-bit int value */
+	RTE_TEL_CONTAINER, /** a container struct */
 };
 
 /**
@@ -136,6 +137,28 @@ __rte_experimental
 int
 rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x);
 
+/**
+ * Add a container to an array. A container is an existing telemetry data
+ * array. The array the container is to be added to must have been started by
+ * rte_tel_data_start_array() with RTE_TEL_CONTAINER as the type parameter.
+ * The container type must be an array of type uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param val
+ *   The pointer to the container to be stored in the array.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep);
+
 /**
  * Add a string value to a dictionary.
  * The dict must have been started by rte_tel_data_start_dict().
@@ -190,6 +213,30 @@ int
 rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 		const char *name, uint64_t val);
 
+/**
+ * Add a container to a dictionary. A container is an existing telemetry data
+ * array. The dict the container is to be added to must have been started by
+ * rte_tel_data_start_dict(). The container must be an array of type
+ * uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param name
+ *   The name the value is to be stored under in the dict.
+ * @param val
+ *   The pointer to the container to be stored in the dict.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep);
+
 /**
  * This telemetry callback is used when registering a telemetry command.
  * It handles getting and formatting information to be returned to telemetry
@@ -264,4 +311,27 @@ int
 rte_telemetry_init(const char *runtime_dir, rte_cpuset_t *cpuset,
 		const char **err_str);
 
+/**
+ * Get a pointer to a container with memory allocated. The container is to be
+ * used embedded within an existing telemetry dict/array.
+ *
+ * @return
+ *  Pointer to a container.
+ */
+__rte_experimental
+struct rte_tel_data *
+rte_tel_data_alloc(void);
+
+/**
+ * @internal
+ * Free a container that has memory allocated.
+ *
+ * @param data
+ *  Pointer to container.
+ *.
+ */
+__rte_experimental
+void
+rte_tel_data_free(struct rte_tel_data *data);
+
 #endif
diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map
index 86433c21d0..d1dbf8d586 100644
--- a/lib/librte_telemetry/rte_telemetry_version.map
+++ b/lib/librte_telemetry/rte_telemetry_version.map
@@ -1,12 +1,16 @@
 EXPERIMENTAL {
 	global:
 
+	rte_tel_data_add_array_container;
 	rte_tel_data_add_array_int;
 	rte_tel_data_add_array_string;
 	rte_tel_data_add_array_u64;
+	rte_tel_data_add_dict_container;
 	rte_tel_data_add_dict_int;
 	rte_tel_data_add_dict_string;
 	rte_tel_data_add_dict_u64;
+	rte_tel_data_alloc;
+	rte_tel_data_free;
 	rte_tel_data_start_array;
 	rte_tel_data_start_dict;
 	rte_tel_data_string;
diff --git a/lib/librte_telemetry/telemetry.c b/lib/librte_telemetry/telemetry.c
index 0252282735..4469885c34 100644
--- a/lib/librte_telemetry/telemetry.c
+++ b/lib/librte_telemetry/telemetry.c
@@ -123,6 +123,35 @@ command_help(const char *cmd __rte_unused, const char *params,
 	return 0;
 }
 
+static int
+container_to_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len)
+{
+	size_t used = 0;
+	unsigned int i;
+
+	if (d->type != RTE_TEL_ARRAY_U64 && d->type != RTE_TEL_ARRAY_INT
+			&& d->type != RTE_TEL_ARRAY_STRING)
+		return snprintf(out_buf, buf_len, "null");
+
+	used = rte_tel_json_empty_array(out_buf, buf_len, 0);
+	if (d->type == RTE_TEL_ARRAY_U64)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_u64(out_buf,
+				buf_len, used,
+				d->data.array[i].u64val);
+	if (d->type == RTE_TEL_ARRAY_INT)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_int(out_buf,
+				buf_len, used,
+				d->data.array[i].ival);
+	if (d->type == RTE_TEL_ARRAY_STRING)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_string(out_buf,
+				buf_len, used,
+				d->data.array[i].sval);
+	return used;
+}
+
 static void
 output_json(const char *cmd, const struct rte_tel_data *d, int s)
 {
@@ -169,6 +198,20 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 						buf_len, used,
 						v->name, v->value.u64val);
 				break;
+			case RTE_TEL_CONTAINER:
+			{
+				char temp[buf_len];
+				const struct container *cont =
+						&v->value.container;
+				if (container_to_json(cont->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_obj_json(
+							cb_data_buf,
+							buf_len, used,
+							v->name, temp);
+				if (!cont->keep)
+					rte_tel_data_free(cont->data);
+			}
 			}
 		}
 		used += prefix_used;
@@ -177,6 +220,7 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 	case RTE_TEL_ARRAY_STRING:
 	case RTE_TEL_ARRAY_INT:
 	case RTE_TEL_ARRAY_U64:
+	case RTE_TEL_ARRAY_CONTAINER:
 		prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
 				MAX_CMD_LEN, cmd);
 		cb_data_buf = &out_buf[prefix_used];
@@ -197,6 +241,18 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 				used = rte_tel_json_add_array_u64(cb_data_buf,
 						buf_len, used,
 						d->data.array[i].u64val);
+			else if (d->type == RTE_TEL_ARRAY_CONTAINER) {
+				char temp[buf_len];
+				const struct container *rec_data =
+						&d->data.array[i].container;
+				if (container_to_json(rec_data->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_array_json(
+							cb_data_buf,
+							buf_len, used, temp);
+				if (!rec_data->keep)
+					rte_tel_data_free(rec_data->data);
+			}
 		used += prefix_used;
 		used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
 		break;
diff --git a/lib/librte_telemetry/telemetry_data.c b/lib/librte_telemetry/telemetry_data.c
index f424bbd48f..77b0fe09a5 100644
--- a/lib/librte_telemetry/telemetry_data.c
+++ b/lib/librte_telemetry/telemetry_data.c
@@ -14,6 +14,7 @@ rte_tel_data_start_array(struct rte_tel_data *d, enum rte_tel_value_type type)
 			RTE_TEL_ARRAY_STRING, /* RTE_TEL_STRING_VAL = 0 */
 			RTE_TEL_ARRAY_INT,    /* RTE_TEL_INT_VAL = 1 */
 			RTE_TEL_ARRAY_U64,    /* RTE_TEL_u64_VAL = 2 */
+			RTE_TEL_ARRAY_CONTAINER, /* RTE_TEL_CONTAINER = 3 */
 	};
 	d->type = array_types[type];
 	d->data_len = 0;
@@ -74,6 +75,23 @@ rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x)
 	return 0;
 }
 
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep)
+{
+	if (d->type != RTE_TEL_ARRAY_CONTAINER ||
+			(val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_ARRAY_ENTRIES)
+		return -ENOSPC;
+
+	d->data.array[d->data_len].container.data = val;
+	d->data.array[d->data_len++].container.keep = !!keep;
+	return 0;
+}
+
 int
 rte_tel_data_add_dict_string(struct rte_tel_data *d, const char *name,
 		const char *val)
@@ -128,3 +146,36 @@ rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
 	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
 }
+
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep)
+{
+	struct tel_dict_entry *e = &d->data.dict[d->data_len];
+
+	if (d->type != RTE_TEL_DICT || (val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES)
+		return -ENOSPC;
+
+	d->data_len++;
+	e->type = RTE_TEL_CONTAINER;
+	e->value.container.data = val;
+	e->value.container.keep = !!keep;
+	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
+	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
+}
+
+struct rte_tel_data *
+rte_tel_data_alloc(void)
+{
+	return malloc(sizeof(struct rte_tel_data));
+}
+
+void
+rte_tel_data_free(struct rte_tel_data *data)
+{
+	free(data);
+}
diff --git a/lib/librte_telemetry/telemetry_data.h b/lib/librte_telemetry/telemetry_data.h
index ff3a371a33..adb84a09f1 100644
--- a/lib/librte_telemetry/telemetry_data.h
+++ b/lib/librte_telemetry/telemetry_data.h
@@ -15,6 +15,12 @@ enum tel_container_types {
 	RTE_TEL_ARRAY_STRING, /** array of string values only */
 	RTE_TEL_ARRAY_INT,    /** array of signed, 32-bit int values */
 	RTE_TEL_ARRAY_U64,    /** array of unsigned 64-bit int values */
+	RTE_TEL_ARRAY_CONTAINER, /** array of container structs */
+};
+
+struct container {
+	struct rte_tel_data *data;
+	int keep;
 };
 
 /* each type here must have an equivalent enum in the value types enum in
@@ -25,6 +31,7 @@ union tel_value {
 	char sval[RTE_TEL_MAX_STRING_LEN];
 	int ival;
 	uint64_t u64val;
+	struct container container;
 };
 
 struct tel_dict_entry {
diff --git a/lib/librte_telemetry/telemetry_json.h b/lib/librte_telemetry/telemetry_json.h
index a2ce4899e0..ad270b9b30 100644
--- a/lib/librte_telemetry/telemetry_json.h
+++ b/lib/librte_telemetry/telemetry_json.h
@@ -102,6 +102,22 @@ rte_tel_json_add_array_u64(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/*
+ * Add a new element with raw JSON value to the JSON array stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_array_json(char *buf, const int len, const int used,
+		const char *str)
+{
+	int ret, end = used - 1; /* strip off final delimiter */
+	if (used <= 2) /* assume empty, since minimum is '[]' */
+		return __json_snprintf(buf, len, "[%s]", str);
+
+	ret = __json_snprintf(buf + end, len - end, ",%s]", str);
+	return ret == 0 ? used : end + ret;
+}
+
 /**
  * Add a new element with uint64_t value to the JSON object stored in the
  * provided buffer.
@@ -155,4 +171,21 @@ rte_tel_json_add_obj_str(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/**
+ * Add a new element with raw JSON value to the JSON object stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_obj_json(char *buf, const int len, const int used,
+		const char *name, const char *val)
+{
+	int ret, end = used - 1;
+	if (used <= 2) /* assume empty, since minimum is '{}' */
+		return __json_snprintf(buf, len, "{\"%s\":%s}", name, val);
+
+	ret = __json_snprintf(buf + end, len - end, ",\"%s\":%s}",
+			name, val);
+	return ret == 0 ? used : end + ret;
+}
+
 #endif /*_RTE_TELEMETRY_JSON_H_*/
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v8 2/3] test/test_telemetry_data: add unit tests for data to JSON
  2020-08-21 12:51 ` [dpdk-dev] [PATCH v8 0/3] add basic ethdev stats with data object recursion Ciara Power
  2020-08-21 12:51   ` [dpdk-dev] [PATCH v8 1/3] telemetry: support array values in data objects Ciara Power
@ 2020-08-21 12:51   ` Ciara Power
  2020-08-21 12:51   ` [dpdk-dev] [PATCH v8 3/3] ethdev: add common stats for telemetry Ciara Power
  2 siblings, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-08-21 12:51 UTC (permalink / raw)
  To: dev
  Cc: keith.wiles, bruce.richardson, Louise Kilheeney, Ciara Power,
	Kevin Laatz

From: Louise Kilheeney <louise.kilheeney@intel.com>

This patch adds tests for verifying telemetry data structures are
converted to JSON as expected. Both flat and recursive data structures
are tested, for all possible value types.

The app connects to the telemetry socket as a client, and registers one
command with a corresponding callback function. Each time the callback
function is called, it copies a global data variable to the data pointer
passed in by telemetry.
When a test case is run, the test case function builds up the global
data variable with the relevant data types, and the expected json string
output which should be generated from that. The 'test_output()' function
is used to trigger the callback and ensure the actual output matches
that expected.

Signed-off-by: Louise Kilheeney <louise.kilheeney@intel.com>
Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
---
 app/test/Makefile              |   1 +
 app/test/meson.build           |   5 +-
 app/test/test_telemetry_data.c | 369 +++++++++++++++++++++++++++++++++
 3 files changed, 373 insertions(+), 2 deletions(-)
 create mode 100644 app/test/test_telemetry_data.c

diff --git a/app/test/Makefile b/app/test/Makefile
index f4065271e4..1cb64089c1 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -145,6 +145,7 @@ SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6.c
 SRCS-$(CONFIG_RTE_LIBRTE_LPM) += test_lpm6_perf.c
 
 SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += test_telemetry_json.c
+SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += test_telemetry_data.c
 
 SRCS-y += test_debug.c
 SRCS-y += test_errno.c
diff --git a/app/test/meson.build b/app/test/meson.build
index 786a213972..4a72fe5b61 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -170,6 +170,7 @@ test_deps = ['acl',
 	'ring',
 	'security',
 	'stack',
+	'telemetry',
 	'timer'
 ]
 
@@ -345,8 +346,8 @@ if dpdk_conf.has('RTE_LIBRTE_SKELETON_EVENTDEV_PMD')
 	test_deps += 'pmd_skeleton_event'
 endif
 if dpdk_conf.has('RTE_LIBRTE_TELEMETRY')
-	test_sources += 'test_telemetry_json.c'
-	fast_tests += [['telemetry_json_autotest', true]]
+	test_sources += ['test_telemetry_json.c', 'test_telemetry_data.c']
+	fast_tests += [['telemetry_json_autotest', true], ['telemetry_data_autotest', true]]
 endif
 
 # The following linkages of drivers are required because
diff --git a/app/test/test_telemetry_data.c b/app/test/test_telemetry_data.c
new file mode 100644
index 0000000000..7a31e68a78
--- /dev/null
+++ b/app/test/test_telemetry_data.c
@@ -0,0 +1,369 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2020 Intel Corporation
+ */
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <rte_eal.h>
+#include <rte_common.h>
+#include <rte_telemetry.h>
+#include <rte_string_fns.h>
+
+#include "test.h"
+#include "telemetry_data.h"
+
+#define TELEMETRY_VERSION "v2"
+#define REQUEST_CMD "/test"
+#define BUF_SIZE 1024
+#define TEST_OUTPUT(exp) test_output(__func__, exp)
+
+static struct rte_tel_data response_data;
+static int sock;
+
+/*
+ * This function is the callback registered with Telemetry to be used when
+ * the /test command is requested. This callback returns the global data built
+ * up by the individual test cases.
+ */
+static int
+test_cb(const char *cmd __rte_unused, const char *params __rte_unused,
+		struct rte_tel_data *d)
+{
+	*d = response_data;
+	return 0;
+}
+
+/*
+ * This function is called by each test case function. It communicates with
+ * the telemetry socket by requesting the /test command, and reading the
+ * response. The expected response is passed in by the test case function,
+ * and is compared to the actual response received from Telemetry.
+ */
+static int
+test_output(const char *func_name, const char *expected)
+{
+	int bytes;
+	char buf[BUF_SIZE * 16];
+	if (write(sock, REQUEST_CMD, strlen(REQUEST_CMD)) < 0) {
+		printf("%s: Error with socket write - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	bytes = read(sock, buf, sizeof(buf) - 1);
+	if (bytes < 0) {
+		printf("%s: Error with socket read - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	buf[bytes] = '\0';
+	printf("%s: buf = '%s', expected = '%s'\n", func_name, buf, expected);
+	return strncmp(expected, buf, sizeof(buf));
+}
+
+static int
+test_dict_with_array_int_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_INT_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_INT_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_int(child_data, i);
+		rte_tel_data_add_array_int(child_data2, i);
+	}
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[0,1,2,3,4],"
+			"\"dict_1\":[0,1,2,3,4]}}");
+}
+
+static int
+test_array_with_array_int_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_INT_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_INT_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_int(child_data, i);
+		rte_tel_data_add_array_int(child_data2, i);
+	}
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[0,1,2,3,4],[0,1,2,3,4]]}");
+}
+
+static int
+test_case_array_int(void)
+{
+	int i;
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_INT_VAL);
+	for (i = 0; i < 5; i++)
+		rte_tel_data_add_array_int(&response_data, i);
+	return TEST_OUTPUT("{\"/test\":[0,1,2,3,4]}");
+}
+
+static int
+test_case_add_dict_int(void)
+{
+	int i = 0;
+	char name_of_value[8];
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		sprintf(name_of_value, "dict_%d", i);
+		rte_tel_data_add_dict_int(&response_data, name_of_value, i);
+	}
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":0,\"dict_1\":1,\"dict_2\":2,"
+			"\"dict_3\":3,\"dict_4\":4}}");
+}
+
+static int
+test_case_array_string(void)
+{
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_STRING_VAL);
+	rte_tel_data_add_array_string(&response_data, "aaaa");
+	rte_tel_data_add_array_string(&response_data, "bbbb");
+	rte_tel_data_add_array_string(&response_data, "cccc");
+	rte_tel_data_add_array_string(&response_data, "dddd");
+	rte_tel_data_add_array_string(&response_data, "eeee");
+
+	return TEST_OUTPUT("{\"/test\":[\"aaaa\",\"bbbb\",\"cccc\",\"dddd\","
+			"\"eeee\"]}");
+}
+
+static int
+test_case_add_dict_string(void)
+{
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	rte_tel_data_add_dict_string(&response_data, "dict_0", "aaaa");
+	rte_tel_data_add_dict_string(&response_data, "dict_1", "bbbb");
+	rte_tel_data_add_dict_string(&response_data, "dict_2", "cccc");
+	rte_tel_data_add_dict_string(&response_data, "dict_3", "dddd");
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":\"aaaa\",\"dict_1\":"
+			"\"bbbb\",\"dict_2\":\"cccc\",\"dict_3\":\"dddd\"}}");
+}
+
+
+static int
+test_dict_with_array_string_values(void)
+{
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_STRING_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_STRING_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	rte_tel_data_add_array_string(child_data, "aaaa");
+	rte_tel_data_add_array_string(child_data2, "bbbb");
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[\"aaaa\"],\"dict_1\":"
+			"[\"bbbb\"]}}");
+}
+
+static int
+test_array_with_array_string_values(void)
+{
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_STRING_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_STRING_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	rte_tel_data_add_array_string(child_data, "aaaa");
+	rte_tel_data_add_array_string(child_data2, "bbbb");
+
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[\"aaaa\"],[\"bbbb\"]]}");
+}
+
+static int
+test_case_array_u64(void)
+{
+	int i;
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_U64_VAL);
+	for (i = 0; i < 5; i++)
+		rte_tel_data_add_array_u64(&response_data, i);
+	return TEST_OUTPUT("{\"/test\":[0,1,2,3,4]}");
+}
+
+static int
+test_case_add_dict_u64(void)
+{
+	int i = 0;
+	char name_of_value[8];
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		sprintf(name_of_value, "dict_%d", i);
+		rte_tel_data_add_dict_u64(&response_data, name_of_value, i);
+	}
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":0,\"dict_1\":1,\"dict_2\":2,"
+			"\"dict_3\":3,\"dict_4\":4}}");
+}
+
+static int
+test_dict_with_array_u64_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_U64_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_U64_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 10; i++) {
+		rte_tel_data_add_array_u64(child_data, i);
+		rte_tel_data_add_array_u64(child_data2, i);
+	}
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[0,1,2,3,4,5,6,7,8,9],"
+			"\"dict_1\":[0,1,2,3,4,5,6,7,8,9]}}");
+}
+
+static int
+test_array_with_array_u64_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_U64_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_U64_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_u64(child_data, i);
+		rte_tel_data_add_array_u64(child_data2, i);
+	}
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[0,1,2,3,4],[0,1,2,3,4]]}");
+}
+
+static int
+connect_to_socket(void)
+{
+	char buf[BUF_SIZE];
+	int sock, bytes;
+	struct sockaddr_un telem_addr;
+
+	sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+	if (sock < 0) {
+		printf("\n%s: Error creating socket: %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	telem_addr.sun_family = AF_UNIX;
+	snprintf(telem_addr.sun_path, sizeof(telem_addr.sun_path),
+			"%s/dpdk_telemetry.%s",	rte_eal_get_runtime_dir(),
+			TELEMETRY_VERSION);
+	if (connect(sock, (struct sockaddr *) &telem_addr,
+			sizeof(telem_addr)) < 0)
+		printf("\n%s: Error connecting to socket: %s\n", __func__,
+				strerror(errno));
+
+	bytes = read(sock, buf, sizeof(buf) - 1);
+	if (bytes < 0) {
+		printf("%s: Error with socket read - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	buf[bytes] = '\0';
+	printf("\n%s: %s\n", __func__, buf);
+	return sock;
+}
+
+static int
+test_telemetry_data(void)
+{
+	typedef int (*test_case)(void);
+	unsigned int i = 0;
+
+	sock = connect_to_socket();
+	if (sock <= 0)
+		return -1;
+
+	test_case test_cases[] = {test_case_array_string,
+			test_case_array_int, test_case_array_u64,
+			test_case_add_dict_int, test_case_add_dict_u64,
+			test_case_add_dict_string,
+			test_dict_with_array_int_values,
+			test_dict_with_array_u64_values,
+			test_dict_with_array_string_values,
+			test_array_with_array_int_values,
+			test_array_with_array_u64_values,
+			test_array_with_array_string_values };
+
+	rte_telemetry_register_cmd(REQUEST_CMD, test_cb, "Test");
+	for (i = 0; i < RTE_DIM(test_cases); i++) {
+		if (test_cases[i]() != 0) {
+			close(sock);
+			return -1;
+		}
+	}
+	close(sock);
+	return 0;
+}
+
+REGISTER_TEST_COMMAND(telemetry_data_autotest, test_telemetry_data);
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v8 3/3] ethdev: add common stats for telemetry
  2020-08-21 12:51 ` [dpdk-dev] [PATCH v8 0/3] add basic ethdev stats with data object recursion Ciara Power
  2020-08-21 12:51   ` [dpdk-dev] [PATCH v8 1/3] telemetry: support array values in data objects Ciara Power
  2020-08-21 12:51   ` [dpdk-dev] [PATCH v8 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
@ 2020-08-21 12:51   ` Ciara Power
  2 siblings, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-08-21 12:51 UTC (permalink / raw)
  To: dev
  Cc: keith.wiles, bruce.richardson, Ciara Power, Thomas Monjalon,
	Ferruh Yigit, Andrew Rybchenko

The ethdev library now registers a telemetry command for common ethdev
statistics.

An example usage is shown below:

Connecting to /var/run/dpdk/rte/dpdk_telemetry.v2
{"version": "DPDK 20.08.0-rc1", "pid": 14119, "max_output_len": 16384}
--> /ethdev/stats,0
{"/ethdev/stats": {"ipackets": 0, "opackets": 0, "ibytes": 0, "obytes": \
    0, "imissed": 0, "ierrors": 0, "oerrors": 0, "rx_nombuf": 0, \
    "q_ipackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_opackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_ibytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_obytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_errors": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}}

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
---
 lib/librte_ethdev/rte_ethdev.c | 53 ++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
index 7858ad5f11..8ee14b1b54 100644
--- a/lib/librte_ethdev/rte_ethdev.c
+++ b/lib/librte_ethdev/rte_ethdev.c
@@ -5275,6 +5275,57 @@ handle_port_list(const char *cmd __rte_unused,
 	return 0;
 }
 
+static void
+add_port_queue_stats(struct rte_tel_data *d, uint64_t *q_stats,
+		const char *stat_name)
+{
+	int q;
+	struct rte_tel_data *q_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(q_data, RTE_TEL_U64_VAL);
+	for (q = 0; q < RTE_ETHDEV_QUEUE_STAT_CNTRS; q++)
+		rte_tel_data_add_array_u64(q_data, q_stats[q]);
+	rte_tel_data_add_dict_container(d, stat_name, q_data, 0);
+}
+
+#define ADD_DICT_STAT(stats, s) rte_tel_data_add_dict_u64(d, #s, stats.s)
+
+static int
+handle_port_stats(const char *cmd __rte_unused,
+		const char *params,
+		struct rte_tel_data *d)
+{
+	struct rte_eth_stats stats;
+	int port_id, ret;
+
+	if (params == NULL || strlen(params) == 0 || !isdigit(*params))
+		return -1;
+
+	port_id = atoi(params);
+	if (!rte_eth_dev_is_valid_port(port_id))
+		return -1;
+
+	ret = rte_eth_stats_get(port_id, &stats);
+	if (ret < 0)
+		return -1;
+
+	rte_tel_data_start_dict(d);
+	ADD_DICT_STAT(stats, ipackets);
+	ADD_DICT_STAT(stats, opackets);
+	ADD_DICT_STAT(stats, ibytes);
+	ADD_DICT_STAT(stats, obytes);
+	ADD_DICT_STAT(stats, imissed);
+	ADD_DICT_STAT(stats, ierrors);
+	ADD_DICT_STAT(stats, oerrors);
+	ADD_DICT_STAT(stats, rx_nombuf);
+	add_port_queue_stats(d, stats.q_ipackets, "q_ipackets");
+	add_port_queue_stats(d, stats.q_opackets, "q_opackets");
+	add_port_queue_stats(d, stats.q_ibytes, "q_ibytes");
+	add_port_queue_stats(d, stats.q_obytes, "q_obytes");
+	add_port_queue_stats(d, stats.q_errors, "q_errors");
+
+	return 0;
+}
+
 static int
 handle_port_xstats(const char *cmd __rte_unused,
 		const char *params,
@@ -5361,6 +5412,8 @@ RTE_INIT(ethdev_init_telemetry)
 {
 	rte_telemetry_register_cmd("/ethdev/list", handle_port_list,
 			"Returns list of available ethdev ports. Takes no parameters");
+	rte_telemetry_register_cmd("/ethdev/stats", handle_port_stats,
+			"Returns the common stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/xstats", handle_port_xstats,
 			"Returns the extended stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/link_status",
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v9 0/3] add basic ethdev stats with data object recursion
  2020-06-12 10:53 [dpdk-dev] [RFC 0/2] add basic ethdev stats with data object recursion Ciara Power
                   ` (8 preceding siblings ...)
  2020-08-21 12:51 ` [dpdk-dev] [PATCH v8 0/3] add basic ethdev stats with data object recursion Ciara Power
@ 2020-09-23 11:12 ` Ciara Power
  2020-09-23 11:12   ` [dpdk-dev] [PATCH v9 1/3] telemetry: support array values in data objects Ciara Power
                     ` (3 more replies)
  9 siblings, 4 replies; 44+ messages in thread
From: Ciara Power @ 2020-09-23 11:12 UTC (permalink / raw)
  To: dev; +Cc: keith.wiles, bruce.richardson, Ciara Power

v9: Rebased onto main to remove conflict.
v8: Rebased onto main.
v7:
  - Simplified connecting to socket by removing use of glob.
  - Fixed buffer overflow issue when reading from socket.
  - Split expected response strings over multiple lines.
v6:
  - Fixed FreeBSD build failure for unit tests.
  - Added comments and expanded commit log.
  - Add loop to call test cases stored in a list.
v5: Added unit tests for telemetry data to JSON conversion.
v4: Added missing param description to Doxygen comment.
v3:
  - Modified commit logs.
  - Changed function names to reference container.
  - Modified Doxygen comments to reference container.
v2:
  - Added support for arrays to have container values.
  - Added support for int and string arrays within dict/array.
  - Added APIs for internal container memory management.

This patchset adds support for basic ethdev statistics in Telemetry.
To do this, recursive data object support is needed to report the queue
statistics in a list. With this patch, an array or dictionary supports
uint64_t, int or string array type values, which is used for the ethdev
queue stats.

Ciara Power (2):
  telemetry: support array values in data objects
  ethdev: add common stats for telemetry

Louise Kilheeney (1):
  test/test_telemetry_data: add unit tests for data to JSON

 app/test/meson.build                          |   5 +-
 app/test/test_telemetry_data.c                | 369 ++++++++++++++++++
 lib/librte_ethdev/rte_ethdev.c                |  53 +++
 lib/librte_telemetry/rte_telemetry.h          |  70 ++++
 .../rte_telemetry_version.map                 |   4 +
 lib/librte_telemetry/telemetry.c              |  56 +++
 lib/librte_telemetry/telemetry_data.c         |  51 +++
 lib/librte_telemetry/telemetry_data.h         |   7 +
 lib/librte_telemetry/telemetry_json.h         |  33 ++
 9 files changed, 646 insertions(+), 2 deletions(-)
 create mode 100644 app/test/test_telemetry_data.c

-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v9 1/3] telemetry: support array values in data objects
  2020-09-23 11:12 ` [dpdk-dev] [PATCH v9 0/3] add basic ethdev stats with data object recursion Ciara Power
@ 2020-09-23 11:12   ` Ciara Power
  2020-09-23 11:12   ` [dpdk-dev] [PATCH v9 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-09-23 11:12 UTC (permalink / raw)
  To: dev
  Cc: keith.wiles, bruce.richardson, Ciara Power, Kevin Laatz,
	Ray Kinsella, Neil Horman

Arrays of type uint64_t/int/string can now be included within an array
or dict. One level of embedded containers is supported. This is
necessary to allow for instances such as the ethdev queue stats to be
reported as a list of uint64_t values, rather than having multiple dict
entries with one uint64_t value for each queue stat.

The memory management APIs provided by telemetry simplify the memory
allocation/free aspect of the embedded container. The rte_tel_data_alloc
function is called in the library/app callback to return a pointer to a
container that has been allocated memory. When adding this container
to an array/dict, a parameter is passed to indicate if the memory
should be freed by telemetry after use. This will allow reuse of the
allocated memory if the library/app wishes to do so.

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
---
 lib/librte_telemetry/rte_telemetry.h          | 70 +++++++++++++++++++
 .../rte_telemetry_version.map                 |  4 ++
 lib/librte_telemetry/telemetry.c              | 56 +++++++++++++++
 lib/librte_telemetry/telemetry_data.c         | 51 ++++++++++++++
 lib/librte_telemetry/telemetry_data.h         |  7 ++
 lib/librte_telemetry/telemetry_json.h         | 33 +++++++++
 6 files changed, 221 insertions(+)

diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h
index deac2e71ca..4693275c24 100644
--- a/lib/librte_telemetry/rte_telemetry.h
+++ b/lib/librte_telemetry/rte_telemetry.h
@@ -47,6 +47,7 @@ enum rte_tel_value_type {
 	RTE_TEL_STRING_VAL, /** a string value */
 	RTE_TEL_INT_VAL,    /** a signed 32-bit int value */
 	RTE_TEL_U64_VAL,    /** an unsigned 64-bit int value */
+	RTE_TEL_CONTAINER, /** a container struct */
 };
 
 /**
@@ -137,6 +138,28 @@ __rte_experimental
 int
 rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x);
 
+/**
+ * Add a container to an array. A container is an existing telemetry data
+ * array. The array the container is to be added to must have been started by
+ * rte_tel_data_start_array() with RTE_TEL_CONTAINER as the type parameter.
+ * The container type must be an array of type uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param val
+ *   The pointer to the container to be stored in the array.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep);
+
 /**
  * Add a string value to a dictionary.
  * The dict must have been started by rte_tel_data_start_dict().
@@ -191,6 +214,30 @@ int
 rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 		const char *name, uint64_t val);
 
+/**
+ * Add a container to a dictionary. A container is an existing telemetry data
+ * array. The dict the container is to be added to must have been started by
+ * rte_tel_data_start_dict(). The container must be an array of type
+ * uint64_t/int/string.
+ *
+ * @param d
+ *   The data structure passed to the callback
+ * @param name
+ *   The name the value is to be stored under in the dict.
+ * @param val
+ *   The pointer to the container to be stored in the dict.
+ * @param keep
+ *   Flag to indicate that the container memory should not be automatically
+ *   freed by the telemetry library once it has finished with the data.
+ *   1 = keep, 0 = free.
+ * @return
+ *   0 on success, negative errno on error
+ */
+__rte_experimental
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep);
+
 /**
  * This telemetry callback is used when registering a telemetry command.
  * It handles getting and formatting information to be returned to telemetry
@@ -265,4 +312,27 @@ int
 rte_telemetry_init(const char *runtime_dir, rte_cpuset_t *cpuset,
 		const char **err_str);
 
+/**
+ * Get a pointer to a container with memory allocated. The container is to be
+ * used embedded within an existing telemetry dict/array.
+ *
+ * @return
+ *  Pointer to a container.
+ */
+__rte_experimental
+struct rte_tel_data *
+rte_tel_data_alloc(void);
+
+/**
+ * @internal
+ * Free a container that has memory allocated.
+ *
+ * @param data
+ *  Pointer to container.
+ *.
+ */
+__rte_experimental
+void
+rte_tel_data_free(struct rte_tel_data *data);
+
 #endif
diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map
index 86433c21d0..d1dbf8d586 100644
--- a/lib/librte_telemetry/rte_telemetry_version.map
+++ b/lib/librte_telemetry/rte_telemetry_version.map
@@ -1,12 +1,16 @@
 EXPERIMENTAL {
 	global:
 
+	rte_tel_data_add_array_container;
 	rte_tel_data_add_array_int;
 	rte_tel_data_add_array_string;
 	rte_tel_data_add_array_u64;
+	rte_tel_data_add_dict_container;
 	rte_tel_data_add_dict_int;
 	rte_tel_data_add_dict_string;
 	rte_tel_data_add_dict_u64;
+	rte_tel_data_alloc;
+	rte_tel_data_free;
 	rte_tel_data_start_array;
 	rte_tel_data_start_dict;
 	rte_tel_data_string;
diff --git a/lib/librte_telemetry/telemetry.c b/lib/librte_telemetry/telemetry.c
index 51e7ceeb1b..caa2c4a535 100644
--- a/lib/librte_telemetry/telemetry.c
+++ b/lib/librte_telemetry/telemetry.c
@@ -133,6 +133,35 @@ command_help(const char *cmd __rte_unused, const char *params,
 	return 0;
 }
 
+static int
+container_to_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len)
+{
+	size_t used = 0;
+	unsigned int i;
+
+	if (d->type != RTE_TEL_ARRAY_U64 && d->type != RTE_TEL_ARRAY_INT
+			&& d->type != RTE_TEL_ARRAY_STRING)
+		return snprintf(out_buf, buf_len, "null");
+
+	used = rte_tel_json_empty_array(out_buf, buf_len, 0);
+	if (d->type == RTE_TEL_ARRAY_U64)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_u64(out_buf,
+				buf_len, used,
+				d->data.array[i].u64val);
+	if (d->type == RTE_TEL_ARRAY_INT)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_int(out_buf,
+				buf_len, used,
+				d->data.array[i].ival);
+	if (d->type == RTE_TEL_ARRAY_STRING)
+		for (i = 0; i < d->data_len; i++)
+			used = rte_tel_json_add_array_string(out_buf,
+				buf_len, used,
+				d->data.array[i].sval);
+	return used;
+}
+
 static void
 output_json(const char *cmd, const struct rte_tel_data *d, int s)
 {
@@ -179,6 +208,20 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 						buf_len, used,
 						v->name, v->value.u64val);
 				break;
+			case RTE_TEL_CONTAINER:
+			{
+				char temp[buf_len];
+				const struct container *cont =
+						&v->value.container;
+				if (container_to_json(cont->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_obj_json(
+							cb_data_buf,
+							buf_len, used,
+							v->name, temp);
+				if (!cont->keep)
+					rte_tel_data_free(cont->data);
+			}
 			}
 		}
 		used += prefix_used;
@@ -187,6 +230,7 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 	case RTE_TEL_ARRAY_STRING:
 	case RTE_TEL_ARRAY_INT:
 	case RTE_TEL_ARRAY_U64:
+	case RTE_TEL_ARRAY_CONTAINER:
 		prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
 				MAX_CMD_LEN, cmd);
 		cb_data_buf = &out_buf[prefix_used];
@@ -207,6 +251,18 @@ output_json(const char *cmd, const struct rte_tel_data *d, int s)
 				used = rte_tel_json_add_array_u64(cb_data_buf,
 						buf_len, used,
 						d->data.array[i].u64val);
+			else if (d->type == RTE_TEL_ARRAY_CONTAINER) {
+				char temp[buf_len];
+				const struct container *rec_data =
+						&d->data.array[i].container;
+				if (container_to_json(rec_data->data,
+						temp, buf_len) != 0)
+					used = rte_tel_json_add_array_json(
+							cb_data_buf,
+							buf_len, used, temp);
+				if (!rec_data->keep)
+					rte_tel_data_free(rec_data->data);
+			}
 		used += prefix_used;
 		used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
 		break;
diff --git a/lib/librte_telemetry/telemetry_data.c b/lib/librte_telemetry/telemetry_data.c
index f424bbd48f..77b0fe09a5 100644
--- a/lib/librte_telemetry/telemetry_data.c
+++ b/lib/librte_telemetry/telemetry_data.c
@@ -14,6 +14,7 @@ rte_tel_data_start_array(struct rte_tel_data *d, enum rte_tel_value_type type)
 			RTE_TEL_ARRAY_STRING, /* RTE_TEL_STRING_VAL = 0 */
 			RTE_TEL_ARRAY_INT,    /* RTE_TEL_INT_VAL = 1 */
 			RTE_TEL_ARRAY_U64,    /* RTE_TEL_u64_VAL = 2 */
+			RTE_TEL_ARRAY_CONTAINER, /* RTE_TEL_CONTAINER = 3 */
 	};
 	d->type = array_types[type];
 	d->data_len = 0;
@@ -74,6 +75,23 @@ rte_tel_data_add_array_u64(struct rte_tel_data *d, uint64_t x)
 	return 0;
 }
 
+int
+rte_tel_data_add_array_container(struct rte_tel_data *d,
+		struct rte_tel_data *val, int keep)
+{
+	if (d->type != RTE_TEL_ARRAY_CONTAINER ||
+			(val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_ARRAY_ENTRIES)
+		return -ENOSPC;
+
+	d->data.array[d->data_len].container.data = val;
+	d->data.array[d->data_len++].container.keep = !!keep;
+	return 0;
+}
+
 int
 rte_tel_data_add_dict_string(struct rte_tel_data *d, const char *name,
 		const char *val)
@@ -128,3 +146,36 @@ rte_tel_data_add_dict_u64(struct rte_tel_data *d,
 	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
 	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
 }
+
+int
+rte_tel_data_add_dict_container(struct rte_tel_data *d, const char *name,
+		struct rte_tel_data *val, int keep)
+{
+	struct tel_dict_entry *e = &d->data.dict[d->data_len];
+
+	if (d->type != RTE_TEL_DICT || (val->type != RTE_TEL_ARRAY_U64
+			&& val->type != RTE_TEL_ARRAY_INT
+			&& val->type != RTE_TEL_ARRAY_STRING))
+		return -EINVAL;
+	if (d->data_len >= RTE_TEL_MAX_DICT_ENTRIES)
+		return -ENOSPC;
+
+	d->data_len++;
+	e->type = RTE_TEL_CONTAINER;
+	e->value.container.data = val;
+	e->value.container.keep = !!keep;
+	const size_t bytes = strlcpy(e->name, name, RTE_TEL_MAX_STRING_LEN);
+	return bytes < RTE_TEL_MAX_STRING_LEN ? 0 : E2BIG;
+}
+
+struct rte_tel_data *
+rte_tel_data_alloc(void)
+{
+	return malloc(sizeof(struct rte_tel_data));
+}
+
+void
+rte_tel_data_free(struct rte_tel_data *data)
+{
+	free(data);
+}
diff --git a/lib/librte_telemetry/telemetry_data.h b/lib/librte_telemetry/telemetry_data.h
index ff3a371a33..adb84a09f1 100644
--- a/lib/librte_telemetry/telemetry_data.h
+++ b/lib/librte_telemetry/telemetry_data.h
@@ -15,6 +15,12 @@ enum tel_container_types {
 	RTE_TEL_ARRAY_STRING, /** array of string values only */
 	RTE_TEL_ARRAY_INT,    /** array of signed, 32-bit int values */
 	RTE_TEL_ARRAY_U64,    /** array of unsigned 64-bit int values */
+	RTE_TEL_ARRAY_CONTAINER, /** array of container structs */
+};
+
+struct container {
+	struct rte_tel_data *data;
+	int keep;
 };
 
 /* each type here must have an equivalent enum in the value types enum in
@@ -25,6 +31,7 @@ union tel_value {
 	char sval[RTE_TEL_MAX_STRING_LEN];
 	int ival;
 	uint64_t u64val;
+	struct container container;
 };
 
 struct tel_dict_entry {
diff --git a/lib/librte_telemetry/telemetry_json.h b/lib/librte_telemetry/telemetry_json.h
index a2ce4899e0..ad270b9b30 100644
--- a/lib/librte_telemetry/telemetry_json.h
+++ b/lib/librte_telemetry/telemetry_json.h
@@ -102,6 +102,22 @@ rte_tel_json_add_array_u64(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/*
+ * Add a new element with raw JSON value to the JSON array stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_array_json(char *buf, const int len, const int used,
+		const char *str)
+{
+	int ret, end = used - 1; /* strip off final delimiter */
+	if (used <= 2) /* assume empty, since minimum is '[]' */
+		return __json_snprintf(buf, len, "[%s]", str);
+
+	ret = __json_snprintf(buf + end, len - end, ",%s]", str);
+	return ret == 0 ? used : end + ret;
+}
+
 /**
  * Add a new element with uint64_t value to the JSON object stored in the
  * provided buffer.
@@ -155,4 +171,21 @@ rte_tel_json_add_obj_str(char *buf, const int len, const int used,
 	return ret == 0 ? used : end + ret;
 }
 
+/**
+ * Add a new element with raw JSON value to the JSON object stored in the
+ * provided buffer.
+ */
+static inline int
+rte_tel_json_add_obj_json(char *buf, const int len, const int used,
+		const char *name, const char *val)
+{
+	int ret, end = used - 1;
+	if (used <= 2) /* assume empty, since minimum is '{}' */
+		return __json_snprintf(buf, len, "{\"%s\":%s}", name, val);
+
+	ret = __json_snprintf(buf + end, len - end, ",\"%s\":%s}",
+			name, val);
+	return ret == 0 ? used : end + ret;
+}
+
 #endif /*_RTE_TELEMETRY_JSON_H_*/
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v9 2/3] test/test_telemetry_data: add unit tests for data to JSON
  2020-09-23 11:12 ` [dpdk-dev] [PATCH v9 0/3] add basic ethdev stats with data object recursion Ciara Power
  2020-09-23 11:12   ` [dpdk-dev] [PATCH v9 1/3] telemetry: support array values in data objects Ciara Power
@ 2020-09-23 11:12   ` Ciara Power
  2020-09-23 11:12   ` [dpdk-dev] [PATCH v9 3/3] ethdev: add common stats for telemetry Ciara Power
  2020-10-06 20:56   ` [dpdk-dev] [PATCH v9 0/3] add basic ethdev stats with data object recursion Thomas Monjalon
  3 siblings, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-09-23 11:12 UTC (permalink / raw)
  To: dev
  Cc: keith.wiles, bruce.richardson, Louise Kilheeney, Ciara Power,
	Kevin Laatz

From: Louise Kilheeney <louise.kilheeney@intel.com>

This patch adds tests for verifying telemetry data structures are
converted to JSON as expected. Both flat and recursive data structures
are tested, for all possible value types.

The app connects to the telemetry socket as a client, and registers one
command with a corresponding callback function. Each time the callback
function is called, it copies a global data variable to the data pointer
passed in by telemetry.
When a test case is run, the test case function builds up the global
data variable with the relevant data types, and the expected json string
output which should be generated from that. The 'test_output()' function
is used to trigger the callback and ensure the actual output matches
that expected.

Signed-off-by: Louise Kilheeney <louise.kilheeney@intel.com>
Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>

---
v9: Removed Makefile entry as this is removed on main branch.
---
 app/test/meson.build           |   5 +-
 app/test/test_telemetry_data.c | 369 +++++++++++++++++++++++++++++++++
 2 files changed, 372 insertions(+), 2 deletions(-)
 create mode 100644 app/test/test_telemetry_data.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 181e870290..dedf29dd7f 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -171,6 +171,7 @@ test_deps = ['acl',
 	'ring',
 	'security',
 	'stack',
+	'telemetry',
 	'timer'
 ]
 
@@ -347,8 +348,8 @@ if dpdk_conf.has('RTE_LIBRTE_SKELETON_EVENTDEV_PMD')
 	test_deps += 'pmd_skeleton_event'
 endif
 if dpdk_conf.has('RTE_LIBRTE_TELEMETRY')
-	test_sources += 'test_telemetry_json.c'
-	fast_tests += [['telemetry_json_autotest', true]]
+	test_sources += ['test_telemetry_json.c', 'test_telemetry_data.c']
+	fast_tests += [['telemetry_json_autotest', true], ['telemetry_data_autotest', true]]
 endif
 
 # The following linkages of drivers are required because
diff --git a/app/test/test_telemetry_data.c b/app/test/test_telemetry_data.c
new file mode 100644
index 0000000000..7a31e68a78
--- /dev/null
+++ b/app/test/test_telemetry_data.c
@@ -0,0 +1,369 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2020 Intel Corporation
+ */
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <rte_eal.h>
+#include <rte_common.h>
+#include <rte_telemetry.h>
+#include <rte_string_fns.h>
+
+#include "test.h"
+#include "telemetry_data.h"
+
+#define TELEMETRY_VERSION "v2"
+#define REQUEST_CMD "/test"
+#define BUF_SIZE 1024
+#define TEST_OUTPUT(exp) test_output(__func__, exp)
+
+static struct rte_tel_data response_data;
+static int sock;
+
+/*
+ * This function is the callback registered with Telemetry to be used when
+ * the /test command is requested. This callback returns the global data built
+ * up by the individual test cases.
+ */
+static int
+test_cb(const char *cmd __rte_unused, const char *params __rte_unused,
+		struct rte_tel_data *d)
+{
+	*d = response_data;
+	return 0;
+}
+
+/*
+ * This function is called by each test case function. It communicates with
+ * the telemetry socket by requesting the /test command, and reading the
+ * response. The expected response is passed in by the test case function,
+ * and is compared to the actual response received from Telemetry.
+ */
+static int
+test_output(const char *func_name, const char *expected)
+{
+	int bytes;
+	char buf[BUF_SIZE * 16];
+	if (write(sock, REQUEST_CMD, strlen(REQUEST_CMD)) < 0) {
+		printf("%s: Error with socket write - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	bytes = read(sock, buf, sizeof(buf) - 1);
+	if (bytes < 0) {
+		printf("%s: Error with socket read - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	buf[bytes] = '\0';
+	printf("%s: buf = '%s', expected = '%s'\n", func_name, buf, expected);
+	return strncmp(expected, buf, sizeof(buf));
+}
+
+static int
+test_dict_with_array_int_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_INT_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_INT_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_int(child_data, i);
+		rte_tel_data_add_array_int(child_data2, i);
+	}
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[0,1,2,3,4],"
+			"\"dict_1\":[0,1,2,3,4]}}");
+}
+
+static int
+test_array_with_array_int_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_INT_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_INT_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_int(child_data, i);
+		rte_tel_data_add_array_int(child_data2, i);
+	}
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[0,1,2,3,4],[0,1,2,3,4]]}");
+}
+
+static int
+test_case_array_int(void)
+{
+	int i;
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_INT_VAL);
+	for (i = 0; i < 5; i++)
+		rte_tel_data_add_array_int(&response_data, i);
+	return TEST_OUTPUT("{\"/test\":[0,1,2,3,4]}");
+}
+
+static int
+test_case_add_dict_int(void)
+{
+	int i = 0;
+	char name_of_value[8];
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		sprintf(name_of_value, "dict_%d", i);
+		rte_tel_data_add_dict_int(&response_data, name_of_value, i);
+	}
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":0,\"dict_1\":1,\"dict_2\":2,"
+			"\"dict_3\":3,\"dict_4\":4}}");
+}
+
+static int
+test_case_array_string(void)
+{
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_STRING_VAL);
+	rte_tel_data_add_array_string(&response_data, "aaaa");
+	rte_tel_data_add_array_string(&response_data, "bbbb");
+	rte_tel_data_add_array_string(&response_data, "cccc");
+	rte_tel_data_add_array_string(&response_data, "dddd");
+	rte_tel_data_add_array_string(&response_data, "eeee");
+
+	return TEST_OUTPUT("{\"/test\":[\"aaaa\",\"bbbb\",\"cccc\",\"dddd\","
+			"\"eeee\"]}");
+}
+
+static int
+test_case_add_dict_string(void)
+{
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	rte_tel_data_add_dict_string(&response_data, "dict_0", "aaaa");
+	rte_tel_data_add_dict_string(&response_data, "dict_1", "bbbb");
+	rte_tel_data_add_dict_string(&response_data, "dict_2", "cccc");
+	rte_tel_data_add_dict_string(&response_data, "dict_3", "dddd");
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":\"aaaa\",\"dict_1\":"
+			"\"bbbb\",\"dict_2\":\"cccc\",\"dict_3\":\"dddd\"}}");
+}
+
+
+static int
+test_dict_with_array_string_values(void)
+{
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_STRING_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_STRING_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	rte_tel_data_add_array_string(child_data, "aaaa");
+	rte_tel_data_add_array_string(child_data2, "bbbb");
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[\"aaaa\"],\"dict_1\":"
+			"[\"bbbb\"]}}");
+}
+
+static int
+test_array_with_array_string_values(void)
+{
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_STRING_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_STRING_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	rte_tel_data_add_array_string(child_data, "aaaa");
+	rte_tel_data_add_array_string(child_data2, "bbbb");
+
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[\"aaaa\"],[\"bbbb\"]]}");
+}
+
+static int
+test_case_array_u64(void)
+{
+	int i;
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_U64_VAL);
+	for (i = 0; i < 5; i++)
+		rte_tel_data_add_array_u64(&response_data, i);
+	return TEST_OUTPUT("{\"/test\":[0,1,2,3,4]}");
+}
+
+static int
+test_case_add_dict_u64(void)
+{
+	int i = 0;
+	char name_of_value[8];
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 5; i++) {
+		sprintf(name_of_value, "dict_%d", i);
+		rte_tel_data_add_dict_u64(&response_data, name_of_value, i);
+	}
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":0,\"dict_1\":1,\"dict_2\":2,"
+			"\"dict_3\":3,\"dict_4\":4}}");
+}
+
+static int
+test_dict_with_array_u64_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_U64_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_U64_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_dict(&response_data);
+
+	for (i = 0; i < 10; i++) {
+		rte_tel_data_add_array_u64(child_data, i);
+		rte_tel_data_add_array_u64(child_data2, i);
+	}
+
+	rte_tel_data_add_dict_container(&response_data, "dict_0",
+	 child_data, 0);
+	rte_tel_data_add_dict_container(&response_data, "dict_1",
+	 child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":{\"dict_0\":[0,1,2,3,4,5,6,7,8,9],"
+			"\"dict_1\":[0,1,2,3,4,5,6,7,8,9]}}");
+}
+
+static int
+test_array_with_array_u64_values(void)
+{
+	int i;
+
+	struct rte_tel_data *child_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data, RTE_TEL_U64_VAL);
+
+	struct rte_tel_data *child_data2 = rte_tel_data_alloc();
+	rte_tel_data_start_array(child_data2, RTE_TEL_U64_VAL);
+
+	memset(&response_data, 0, sizeof(response_data));
+	rte_tel_data_start_array(&response_data, RTE_TEL_CONTAINER);
+
+	for (i = 0; i < 5; i++) {
+		rte_tel_data_add_array_u64(child_data, i);
+		rte_tel_data_add_array_u64(child_data2, i);
+	}
+	rte_tel_data_add_array_container(&response_data, child_data, 0);
+	rte_tel_data_add_array_container(&response_data, child_data2, 0);
+
+	return TEST_OUTPUT("{\"/test\":[[0,1,2,3,4],[0,1,2,3,4]]}");
+}
+
+static int
+connect_to_socket(void)
+{
+	char buf[BUF_SIZE];
+	int sock, bytes;
+	struct sockaddr_un telem_addr;
+
+	sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+	if (sock < 0) {
+		printf("\n%s: Error creating socket: %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	telem_addr.sun_family = AF_UNIX;
+	snprintf(telem_addr.sun_path, sizeof(telem_addr.sun_path),
+			"%s/dpdk_telemetry.%s",	rte_eal_get_runtime_dir(),
+			TELEMETRY_VERSION);
+	if (connect(sock, (struct sockaddr *) &telem_addr,
+			sizeof(telem_addr)) < 0)
+		printf("\n%s: Error connecting to socket: %s\n", __func__,
+				strerror(errno));
+
+	bytes = read(sock, buf, sizeof(buf) - 1);
+	if (bytes < 0) {
+		printf("%s: Error with socket read - %s\n", __func__,
+				strerror(errno));
+		return -1;
+	}
+	buf[bytes] = '\0';
+	printf("\n%s: %s\n", __func__, buf);
+	return sock;
+}
+
+static int
+test_telemetry_data(void)
+{
+	typedef int (*test_case)(void);
+	unsigned int i = 0;
+
+	sock = connect_to_socket();
+	if (sock <= 0)
+		return -1;
+
+	test_case test_cases[] = {test_case_array_string,
+			test_case_array_int, test_case_array_u64,
+			test_case_add_dict_int, test_case_add_dict_u64,
+			test_case_add_dict_string,
+			test_dict_with_array_int_values,
+			test_dict_with_array_u64_values,
+			test_dict_with_array_string_values,
+			test_array_with_array_int_values,
+			test_array_with_array_u64_values,
+			test_array_with_array_string_values };
+
+	rte_telemetry_register_cmd(REQUEST_CMD, test_cb, "Test");
+	for (i = 0; i < RTE_DIM(test_cases); i++) {
+		if (test_cases[i]() != 0) {
+			close(sock);
+			return -1;
+		}
+	}
+	close(sock);
+	return 0;
+}
+
+REGISTER_TEST_COMMAND(telemetry_data_autotest, test_telemetry_data);
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* [dpdk-dev] [PATCH v9 3/3] ethdev: add common stats for telemetry
  2020-09-23 11:12 ` [dpdk-dev] [PATCH v9 0/3] add basic ethdev stats with data object recursion Ciara Power
  2020-09-23 11:12   ` [dpdk-dev] [PATCH v9 1/3] telemetry: support array values in data objects Ciara Power
  2020-09-23 11:12   ` [dpdk-dev] [PATCH v9 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
@ 2020-09-23 11:12   ` Ciara Power
  2020-10-06 20:56   ` [dpdk-dev] [PATCH v9 0/3] add basic ethdev stats with data object recursion Thomas Monjalon
  3 siblings, 0 replies; 44+ messages in thread
From: Ciara Power @ 2020-09-23 11:12 UTC (permalink / raw)
  To: dev
  Cc: keith.wiles, bruce.richardson, Ciara Power, Thomas Monjalon,
	Ferruh Yigit, Andrew Rybchenko

The ethdev library now registers a telemetry command for common ethdev
statistics.

An example usage is shown below:

Connecting to /var/run/dpdk/rte/dpdk_telemetry.v2
{"version": "DPDK 20.08.0-rc1", "pid": 14119, "max_output_len": 16384}
--> /ethdev/stats,0
{"/ethdev/stats": {"ipackets": 0, "opackets": 0, "ibytes": 0, "obytes": \
    0, "imissed": 0, "ierrors": 0, "oerrors": 0, "rx_nombuf": 0, \
    "q_ipackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_opackets": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_ibytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_obytes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \
    "q_errors": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}}

Signed-off-by: Ciara Power <ciara.power@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
---
 lib/librte_ethdev/rte_ethdev.c | 53 ++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/lib/librte_ethdev/rte_ethdev.c b/lib/librte_ethdev/rte_ethdev.c
index dfe5c1b488..81bb86c433 100644
--- a/lib/librte_ethdev/rte_ethdev.c
+++ b/lib/librte_ethdev/rte_ethdev.c
@@ -5328,6 +5328,57 @@ handle_port_list(const char *cmd __rte_unused,
 	return 0;
 }
 
+static void
+add_port_queue_stats(struct rte_tel_data *d, uint64_t *q_stats,
+		const char *stat_name)
+{
+	int q;
+	struct rte_tel_data *q_data = rte_tel_data_alloc();
+	rte_tel_data_start_array(q_data, RTE_TEL_U64_VAL);
+	for (q = 0; q < RTE_ETHDEV_QUEUE_STAT_CNTRS; q++)
+		rte_tel_data_add_array_u64(q_data, q_stats[q]);
+	rte_tel_data_add_dict_container(d, stat_name, q_data, 0);
+}
+
+#define ADD_DICT_STAT(stats, s) rte_tel_data_add_dict_u64(d, #s, stats.s)
+
+static int
+handle_port_stats(const char *cmd __rte_unused,
+		const char *params,
+		struct rte_tel_data *d)
+{
+	struct rte_eth_stats stats;
+	int port_id, ret;
+
+	if (params == NULL || strlen(params) == 0 || !isdigit(*params))
+		return -1;
+
+	port_id = atoi(params);
+	if (!rte_eth_dev_is_valid_port(port_id))
+		return -1;
+
+	ret = rte_eth_stats_get(port_id, &stats);
+	if (ret < 0)
+		return -1;
+
+	rte_tel_data_start_dict(d);
+	ADD_DICT_STAT(stats, ipackets);
+	ADD_DICT_STAT(stats, opackets);
+	ADD_DICT_STAT(stats, ibytes);
+	ADD_DICT_STAT(stats, obytes);
+	ADD_DICT_STAT(stats, imissed);
+	ADD_DICT_STAT(stats, ierrors);
+	ADD_DICT_STAT(stats, oerrors);
+	ADD_DICT_STAT(stats, rx_nombuf);
+	add_port_queue_stats(d, stats.q_ipackets, "q_ipackets");
+	add_port_queue_stats(d, stats.q_opackets, "q_opackets");
+	add_port_queue_stats(d, stats.q_ibytes, "q_ibytes");
+	add_port_queue_stats(d, stats.q_obytes, "q_obytes");
+	add_port_queue_stats(d, stats.q_errors, "q_errors");
+
+	return 0;
+}
+
 static int
 handle_port_xstats(const char *cmd __rte_unused,
 		const char *params,
@@ -5414,6 +5465,8 @@ RTE_INIT(ethdev_init_telemetry)
 {
 	rte_telemetry_register_cmd("/ethdev/list", handle_port_list,
 			"Returns list of available ethdev ports. Takes no parameters");
+	rte_telemetry_register_cmd("/ethdev/stats", handle_port_stats,
+			"Returns the common stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/xstats", handle_port_xstats,
 			"Returns the extended stats for a port. Parameters: int port_id");
 	rte_telemetry_register_cmd("/ethdev/link_status",
-- 
2.17.1


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [dpdk-dev] [PATCH v9 0/3] add basic ethdev stats with data object recursion
  2020-09-23 11:12 ` [dpdk-dev] [PATCH v9 0/3] add basic ethdev stats with data object recursion Ciara Power
                     ` (2 preceding siblings ...)
  2020-09-23 11:12   ` [dpdk-dev] [PATCH v9 3/3] ethdev: add common stats for telemetry Ciara Power
@ 2020-10-06 20:56   ` Thomas Monjalon
  3 siblings, 0 replies; 44+ messages in thread
From: Thomas Monjalon @ 2020-10-06 20:56 UTC (permalink / raw)
  To: Ciara Power; +Cc: dev, keith.wiles, bruce.richardson, Kilheeney, Louise

> This patchset adds support for basic ethdev statistics in Telemetry.
> To do this, recursive data object support is needed to report the queue
> statistics in a list. With this patch, an array or dictionary supports
> uint64_t, int or string array type values, which is used for the ethdev
> queue stats.

Applied, thanks



^ permalink raw reply	[flat|nested] 44+ messages in thread

end of thread, other threads:[~2020-10-06 20:56 UTC | newest]

Thread overview: 44+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-06-12 10:53 [dpdk-dev] [RFC 0/2] add basic ethdev stats with data object recursion Ciara Power
2020-06-12 10:53 ` [dpdk-dev] [RFC 1/2] telemetry: support some recursive data objects Ciara Power
2020-06-12 12:58   ` Bruce Richardson
2020-06-12 13:07   ` Bruce Richardson
2020-06-12 13:14     ` Bruce Richardson
2020-06-12 10:53 ` [dpdk-dev] [RFC 2/2] ethdev: add basic stats for telemetry Ciara Power
2020-06-12 13:10   ` Bruce Richardson
2020-06-24 13:48 ` [dpdk-dev] [PATCH v2 0/2] add basic ethdev stats with data object recursion Ciara Power
2020-06-24 13:48   ` [dpdk-dev] [PATCH v2 1/2] telemetry: support array values in data objects Ciara Power
2020-06-24 15:09     ` Bruce Richardson
2020-06-24 15:18     ` Bruce Richardson
2020-06-24 13:48   ` [dpdk-dev] [PATCH v2 2/2] ethdev: add basic stats for telemetry Ciara Power
2020-06-24 15:19     ` Bruce Richardson
2020-07-02 10:19 ` [dpdk-dev] [PATCH v3 0/2] add basic ethdev stats with data object recursion Ciara Power
2020-07-02 10:19   ` [dpdk-dev] [PATCH v3 1/2] telemetry: support array values in data objects Ciara Power
2020-07-07  7:15     ` Thomas Monjalon
2020-07-02 10:19   ` [dpdk-dev] [PATCH v3 2/2] ethdev: add common stats for telemetry Ciara Power
2020-07-13 14:23 ` [dpdk-dev] [PATCH v4 0/2] add basic ethdev stats with data object recursion Ciara Power
2020-07-13 14:23   ` [dpdk-dev] [PATCH v4 1/2] telemetry: support array values in data objects Ciara Power
2020-07-13 14:23   ` [dpdk-dev] [PATCH v4 2/2] ethdev: add common stats for telemetry Ciara Power
2020-07-15 12:29 ` [dpdk-dev] [PATCH v5 0/3] add basic ethdev stats with data object recursion Ciara Power
2020-07-15 12:29   ` [dpdk-dev] [PATCH v5 1/3] telemetry: support array values in data objects Ciara Power
2020-07-15 12:29   ` [dpdk-dev] [PATCH v5 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
2020-07-17 11:53     ` Bruce Richardson
2020-07-15 12:29   ` [dpdk-dev] [PATCH v5 3/3] ethdev: add common stats for telemetry Ciara Power
2020-07-20 11:19 ` [dpdk-dev] [PATCH v6 0/3] add basic ethdev stats with data object recursion Ciara Power
2020-07-20 11:19   ` [dpdk-dev] [PATCH v6 1/3] telemetry: support array values in data objects Ciara Power
2020-07-20 11:19   ` [dpdk-dev] [PATCH v6 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
2020-07-20 13:08     ` Bruce Richardson
2020-07-20 11:19   ` [dpdk-dev] [PATCH v6 3/3] ethdev: add common stats for telemetry Ciara Power
2020-07-20 14:04 ` [dpdk-dev] [PATCH v7 0/3] add basic ethdev stats with data object recursion Ciara Power
2020-07-20 14:04   ` [dpdk-dev] [PATCH v7 1/3] telemetry: support array values in data objects Ciara Power
2020-07-20 14:04   ` [dpdk-dev] [PATCH v7 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
2020-07-20 14:32     ` Bruce Richardson
2020-07-20 14:04   ` [dpdk-dev] [PATCH v7 3/3] ethdev: add common stats for telemetry Ciara Power
2020-08-21 12:51 ` [dpdk-dev] [PATCH v8 0/3] add basic ethdev stats with data object recursion Ciara Power
2020-08-21 12:51   ` [dpdk-dev] [PATCH v8 1/3] telemetry: support array values in data objects Ciara Power
2020-08-21 12:51   ` [dpdk-dev] [PATCH v8 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
2020-08-21 12:51   ` [dpdk-dev] [PATCH v8 3/3] ethdev: add common stats for telemetry Ciara Power
2020-09-23 11:12 ` [dpdk-dev] [PATCH v9 0/3] add basic ethdev stats with data object recursion Ciara Power
2020-09-23 11:12   ` [dpdk-dev] [PATCH v9 1/3] telemetry: support array values in data objects Ciara Power
2020-09-23 11:12   ` [dpdk-dev] [PATCH v9 2/3] test/test_telemetry_data: add unit tests for data to JSON Ciara Power
2020-09-23 11:12   ` [dpdk-dev] [PATCH v9 3/3] ethdev: add common stats for telemetry Ciara Power
2020-10-06 20:56   ` [dpdk-dev] [PATCH v9 0/3] add basic ethdev stats with data object recursion Thomas Monjalon

DPDK patches and discussions

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://inbox.dpdk.org/dev/0 dev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 dev dev/ https://inbox.dpdk.org/dev \
		dev@dpdk.org
	public-inbox-index dev

Example config snippet for mirrors.
Newsgroup available over NNTP:
	nntp://inbox.dpdk.org/inbox.dpdk.dev


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git