new file mode 100644
@@ -0,0 +1,578 @@
+From 4c7660827b471ecd862122aa0e2a90c0a0f8ec97 Mon Sep 17 00:00:00 2001
+From: Emma Kenny <emma.kenny@intel.com>
+Date: Tue, 21 Aug 2018 15:49:15 +0100
+Subject: [PATCH v1] dpdk_telemetry: add plugin for DPDK metrics via DPDK
+ Telemetry library
+
+This patch introduces a new plugin for collectd, which consumes DPDK metrics
+via the dpdk_telemetry library. The collectd plugin here provides an
+easy way to use the DPDK telemetry API to query ethernet device metrics.
+
+The collectd plugin retrieves metrics from a DPDK packet forwarding
+application by sending a JSON formatted message via a UNIX domain
+socket. The DPDK telemetry component will respond with a JSON formatted
+reply, delivering the requested metrics. The dpdk_telemetry collectd
+plugin parses the JSON data, and publishes the metric values to collectd
+for further use.
+
+This plugin has a dependency on the DPDK Telemetry library, as it must be
+"in sync" with the DPDK Telemetry implementation.
+
+This patch should be applied on the following collectd commit:
+fff795c9846bd8fe4bc7f76bcd83a2b8cefb4525
+
+Signed-off-by: Emma Kenny <emma.kenny@intel.com>
+Signed-off-by: Brian Archbold <brian.archbold@intel.com>
+---
+ Makefile.am | 7 +
+ configure.ac | 4 +
+ src/collectd.conf.in | 26 ++--
+ src/collectd.conf.pod | 35 +++++
+ src/dpdk_telemetry.c | 364 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ src/types.db | 1 +
+ 6 files changed, 427 insertions(+), 10 deletions(-)
+ create mode 100755 src/dpdk_telemetry.c
+
+diff --git a/Makefile.am b/Makefile.am
+index cb40148..66885e2 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -884,6 +884,13 @@ dpdkstat_la_CFLAGS = $(AM_CFLAGS) $(LIBDPDK_CFLAGS)
+ dpdkstat_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(LIBDPDK_LDFLAGS)
+ dpdkstat_la_LIBADD = $(LIBDPDK_LIBS)
+ endif
++if BUILD_PLUGIN_DPDK_TELEMETRY
++pkglib_LTLIBRARIES += dpdk_telemetry.la
++dpdk_telemetry_la_SOURCES = src/dpdk_telemetry.c
++dpdk_telemetry_la_CFLAGS = $(AM_CFLAGS)
++dpdk_telemetry_la_LDFLAGS = $(PLUGIN_LDFLAGS)
++dpdk_telemetry_la_LIBADD = -ljansson
++endif
+
+ if BUILD_PLUGIN_DRBD
+ pkglib_LTLIBRARIES += drbd.la
+diff --git a/configure.ac b/configure.ac
+index 7bf3718..93ee1a0 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -6289,6 +6289,7 @@ plugin_disk="no"
+ plugin_drbd="no"
+ plugin_dpdkevents="no"
+ plugin_dpdkstat="no"
++plugin_dpdk_telemetry="no"
+ plugin_entropy="no"
+ plugin_ethstat="no"
+ plugin_fhcount="no"
+@@ -6349,6 +6350,7 @@ if test "x$ac_system" = "xLinux"; then
+ plugin_cpufreq="yes"
+ plugin_disk="yes"
+ plugin_drbd="yes"
++ plugin_dpdk_telemetry="yes"
+ plugin_entropy="yes"
+ plugin_fhcount="yes"
+ plugin_fscache="yes"
+@@ -6710,6 +6712,7 @@ AC_PLUGIN([dns], [$with_libpcap], [DNS traffic analysi
+ AC_PLUGIN([dpdkevents], [$plugin_dpdkevents], [Events from DPDK])
+ AC_PLUGIN([dpdkstat], [$plugin_dpdkstat], [Stats from DPDK])
+ AC_PLUGIN([drbd], [$plugin_drbd], [DRBD statistics])
++AC_PLUGIN([dpdk_telemetry], [$plugin_dpdk_telemetry], [Metrics from DPDK Telemetry])
+ AC_PLUGIN([email], [yes], [EMail statistics])
+ AC_PLUGIN([entropy], [$plugin_entropy], [Entropy statistics])
+ AC_PLUGIN([ethstat], [$plugin_ethstat], [Stats from NIC driver])
+@@ -7132,6 +7135,7 @@ AC_MSG_RESULT([ dns . . . . . . . . . $enable_dns])
+ AC_MSG_RESULT([ dpdkevents. . . . . . $enable_dpdkevents])
+ AC_MSG_RESULT([ dpdkstat . . . . . . $enable_dpdkstat])
+ AC_MSG_RESULT([ drbd . . . . . . . . $enable_drbd])
++AC_MSG_RESULT([ dpdk_telemetry. . . . $enable_dpdk_telemetry])
+ AC_MSG_RESULT([ email . . . . . . . . $enable_email])
+ AC_MSG_RESULT([ entropy . . . . . . . $enable_entropy])
+ AC_MSG_RESULT([ ethstat . . . . . . . $enable_ethstat])
+diff --git a/src/collectd.conf.in b/src/collectd.conf.in
+index af65214..8e9d500 100644
+--- a/src/collectd.conf.in
++++ b/src/collectd.conf.in
+@@ -62,12 +62,12 @@
+ @LOAD_PLUGIN_LOGFILE@LoadPlugin logfile
+ @LOAD_PLUGIN_LOG_LOGSTASH@LoadPlugin log_logstash
+
+-#<Plugin logfile>
+-# LogLevel @DEFAULT_LOG_LEVEL@
+-# File STDOUT
+-# Timestamp true
+-# PrintSeverity false
+-#</Plugin>
++<Plugin logfile>
++ LogLevel @DEFAULT_LOG_LEVEL@
++ File STDOUT
++ Timestamp true
++ PrintSeverity false
++</Plugin>
+
+ #<Plugin log_logstash>
+ # LogLevel @DEFAULT_LOG_LEVEL@
+@@ -117,6 +117,7 @@
+ #@BUILD_PLUGIN_DNS_TRUE@LoadPlugin dns
+ #@BUILD_PLUGIN_DPDKEVENTS_TRUE@LoadPlugin dpdkevents
+ #@BUILD_PLUGIN_DPDKSTAT_TRUE@LoadPlugin dpdkstat
++@BUILD_PLUGIN_DPDK_TELEMETRY_TRUE@LoadPlugin dpdk_telemetry
+ #@BUILD_PLUGIN_DRBD_TRUE@LoadPlugin drbd
+ #@BUILD_PLUGIN_EMAIL_TRUE@LoadPlugin email
+ #@BUILD_PLUGIN_ENTROPY_TRUE@LoadPlugin entropy
+@@ -394,10 +395,10 @@
+ # SubtractGuestState true
+ #</Plugin>
+ #
+-#<Plugin csv>
+-# DataDir "@localstatedir@/lib/@PACKAGE_NAME@/csv"
+-# StoreRates false
+-#</Plugin>
++<Plugin csv>
++ DataDir "@localstatedir@/lib/@PACKAGE_NAME@/csv"
++ StoreRates false
++</Plugin>
+
+ #<Plugin curl>
+ # <Page "stock_quotes">
+@@ -595,6 +596,11 @@
+ # PortName "interface2"
+ #</Plugin>
+
++<Plugin dpdk_telemetry>
++ ClientSocketPath "/var/run/.client"
++ DpdkSocketPath "/var/run/.rte_telemetry"
++</Plugin>
++
+ #<Plugin email>
+ # SocketFile "@localstatedir@/run/@PACKAGE_NAME@-email"
+ # SocketGroup "collectd"
+diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod
+index 6e6d6ea..3cc062f 100644
+--- a/src/collectd.conf.pod
++++ b/src/collectd.conf.pod
+@@ -2822,6 +2822,41 @@ convention will be used for the additional ports.
+
+ =back
+
++=head2 Plugin C<dpdk_telemetry>
++
++
++The I< dpdk_telemetry > plugin collects DPDK metrics via the dpdk_telemetry library.
++
++
++B<Synopsis:>
++
++
++ <Plugin dpdk_telemetry>
++ ClientSocketPath "/var/run/.client"
++ DpdkSocketPath "/var/run/.rte_telemetry"
++ </Plugin>
++
++
++B<Options:>
++
++
++=over 2
++
++
++=item B<ClientSocketPath> I<Client_Path>
++
++
++The path to the client socket.
++
++
++=item B<DpdkSocketPath> I<Dpdk_Path>
++
++
++The path to DPDK Telemetry.
++
++
++=back
++
+ =head2 Plugin C<email>
+
+ =over 4
+diff --git a/src/dpdk_telemetry.c b/src/dpdk_telemetry.c
+new file mode 100755
+index 0000000..773256e
+--- /dev/null
++++ b/src/dpdk_telemetry.c
+@@ -0,0 +1,364 @@
++/*-
++ * collectd - src/dpdk_telemetry.c
++ * MIT License
++ *
++ * Copyright(c) 2018 Intel Corporation. All rights reserved.
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a copy
++ * of
++ * this software and associated documentation files (the "Software"), to deal in
++ * the Software without restriction, including without limitation the rights to
++ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
++ * of the Software, and to permit persons to whom the Software is furnished to
++ * do
++ * so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be included in
++ * all
++ * copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ *
++ */
++
++#include "collectd.h"
++#include "common.h"
++#include "plugin.h"
++#include "utils_time.h"
++
++#include <errno.h>
++#include <jansson.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <sys/queue.h>
++#include <sys/socket.h>
++#include <sys/un.h>
++#include <sys/unistd.h>
++
++#define BUF_SIZE 1000000
++#define PLUGIN_NAME "dpdk_telemetry"
++#define DEFAULT_DPDK_PATH "/var/run/.rte_telemetry"
++#define DEFAULT_CLIENT_PATH "/var/run/.client"
++
++struct client_info {
++ int s_send;
++ int s_recv;
++ int fd;
++ const char *dpdk_path;
++ const char *client_path;
++ struct sockaddr_un addr;
++ struct sockaddr_un addrs;
++};
++
++static struct client_info *client = NULL;
++static char g_client_path[BUF_SIZE];
++static char g_dpdk_path[BUF_SIZE];
++
++static int dpdk_telemetry_config(oconfig_item_t *ci) {
++ int ret, i;
++
++ INFO(PLUGIN_NAME ": %s:%d", __FUNCTION__, __LINE__);
++
++ for (i = 0; i < ci->children_num; i++) {
++ oconfig_item_t *child = ci->children + i;
++
++ if (strcasecmp("ClientSocketPath", child->key) == 0) {
++ ret = cf_util_get_string_buffer(child, g_client_path,
++ sizeof(g_client_path));
++ } else if (strcasecmp("DpdkSocketPath", child->key) == 0) {
++ ret = cf_util_get_string_buffer(child, g_dpdk_path, sizeof(g_dpdk_path));
++ } else {
++ ERROR(PLUGIN_NAME ": Unknown configuration parameter"
++ "\"%s\"",
++ child->key);
++ ret = -1;
++ }
++
++ if (ret < 0) {
++ INFO(PLUGIN_NAME ": %s:%d ret =%d", __FUNCTION__, __LINE__, ret);
++ return ret;
++ }
++ }
++ return 0;
++}
++
++static int dpdk_telemetry_parse(json_t *stats, json_t *port, int portid) {
++ json_t *statsArrayObj;
++ if (!stats) {
++ ERROR("dpdk_telemetry: Stats pointer is invalid\n");
++ return -1;
++ }
++
++ if (!port) {
++ ERROR("dpdk_telemetry: Port pointer is invalid\n");
++ return -1;
++ }
++
++ if (portid < 0) {
++ ERROR("dpdk_telemetry: portid is invalid\n");
++ return -1;
++ }
++
++ json_t *name, *value;
++ const char *name_string;
++ int value_int, statslen, i;
++ statslen = json_array_size(stats);
++ for (i = 0; i < statslen; i++) {
++ statsArrayObj = json_array_get(stats, i);
++ name = json_object_get(statsArrayObj, "name");
++ value = json_object_get(statsArrayObj, "value");
++ if (!name) {
++ ERROR("dpdk_telemetry: Request does not have name field\n");
++ return -1;
++ }
++ if (!json_is_string(name)) {
++ ERROR("dpdk_telemetry: Stat name value is not a string\n");
++ return -1;
++ }
++ name_string = json_string_value(name);
++ if (!value) {
++ ERROR("dpdk_telemetry: Request does not have value name\n");
++ return -1;
++ }
++ if (!json_is_integer(value)) {
++ ERROR("dpdk_telemetry: Stat value is not an integer\n");
++ return -1;
++ }
++
++ char dev_name[BUF_SIZE];
++ snprintf(dev_name, sizeof(dev_name), "%s.%d", name_string, portid);
++ value_int = json_integer_value(value);
++ value_t dpdk_telemetry_values[1];
++ value_list_t dpdk_telemetry_vl = VALUE_LIST_INIT;
++ dpdk_telemetry_values[0].counter = value_int;
++ dpdk_telemetry_vl.values = dpdk_telemetry_values;
++ dpdk_telemetry_vl.values_len = 1;
++ dpdk_telemetry_vl.time = cdtime();
++ snprintf(dpdk_telemetry_vl.host, sizeof(dpdk_telemetry_vl.host), "%s",
++ hostname_g);
++ snprintf(dpdk_telemetry_vl.plugin, sizeof(dpdk_telemetry_vl.plugin),
++ "dpdk_telemetry");
++ snprintf(dpdk_telemetry_vl.plugin_instance,
++ sizeof(dpdk_telemetry_vl.plugin_instance), "%s", dev_name);
++ snprintf(dpdk_telemetry_vl.type, sizeof(dpdk_telemetry_vl.type),
++ "dpdk_telemetry");
++ snprintf(dpdk_telemetry_vl.type_instance,
++ sizeof(dpdk_telemetry_vl.type_instance), "%s", name_string);
++
++ int ret = plugin_dispatch_values(&dpdk_telemetry_vl);
++ if (ret < 0) {
++ ERROR("dpdk_telemetry: Failed to dispatch values");
++ return -1;
++ }
++ }
++ return 0;
++}
++
++static int parse_json(char *buf) {
++
++ if (!buf) {
++ ERROR("dpdk_telemetry: buf pointer is invalid\n");
++ return -1;
++ }
++ json_error_t error;
++ json_t *root = json_loads(buf, 0, &error);
++ int arraylen, i;
++ json_t *status, *dataArray, *stats, *dataArrayObj;
++ stats = NULL;
++
++ if (!root) {
++ ERROR("dpdk_telemetry: Could not load JSON object from data passed in"
++ " : %s\n",
++ error.text);
++ return -1;
++ } else if (!json_is_object(root)) {
++ ERROR("dpdk_telemetry: JSON Request is not a JSON object\n");
++ json_decref(root);
++ return -1;
++ }
++
++ status = json_object_get(root, "status_code");
++ if (!status) {
++ ERROR("dpdk_telemetry: Request does not have status field\n");
++ return -1;
++ } else if (!json_is_string(status)) {
++ ERROR("dpdk_telemetry: Status value is not a string\n");
++ return -1;
++ }
++ dataArray = json_object_get(root, "data");
++ if (!dataArray) {
++ ERROR("dpdk_telemetry: Request does not have data field\n");
++ return -1;
++ }
++ arraylen = json_array_size(dataArray);
++ if (!arraylen) {
++ ERROR("dpdk_telemetry: No data to get\n");
++ return -1;
++ }
++
++ for (i = 0; i < arraylen; i++) {
++ json_t *port;
++ dataArrayObj = json_array_get(dataArray, i);
++ port = json_object_get(dataArrayObj, "port");
++ stats = json_object_get(dataArrayObj, "stats");
++ if (!port) {
++ ERROR("dpdk_telemetry: Request does not have port field\n");
++ return -1;
++ }
++ if (!json_is_integer(port)) {
++ ERROR("dpdk_telemetry: Port value is not an integer\n");
++ return -1;
++ }
++
++ if (!stats) {
++ ERROR("dpdk_telemetry: Request does not have stats field\n");
++ return -1;
++ }
++ dpdk_telemetry_parse(stats, port, i);
++ }
++ return 0;
++}
++
++static int dpdk_telemetry_cleanup(void) {
++ if (!client) {
++ WARNING("dpdk_telemetry: instance pointer is NULL, cleanup() has already "
++ "been called\n");
++ return -1;
++ }
++ close(client->s_send);
++ close(client->s_recv);
++ close(client->fd);
++ free(client);
++ client = NULL;
++ return 0;
++}
++
++static int dpdk_telemetry_read(user_data_t *ud) {
++ INFO(PLUGIN_NAME ": %s:%d", __FUNCTION__, __LINE__);
++ struct client_info *client = (struct client_info *)ud->data;
++ char buffer[BUF_SIZE];
++ int bytes, ret;
++ char *json_string = "{\"action\":0,\"command\":"
++ "\"ports_all_stat_values\",\"data\":null}";
++ if (send(client->fd, json_string, strlen(json_string), 0) < 0) {
++ ERROR("dpdk_telemetry: Could not send stats\n");
++ return -1;
++ }
++ bytes = recv(client->fd, buffer, sizeof(buffer), 0);
++ buffer[bytes] = '\0';
++ if (bytes < 0) {
++ ERROR("dpdk_telemetry: Could not receive stats\n");
++ return -1;
++ }
++ ret = parse_json(buffer);
++ if (ret < 0) {
++ ERROR("dpdk_telemetry: Parsing failed\n");
++ return -1;
++ }
++ return 0;
++}
++
++static int dpdk_telemetry_init(void) {
++ INFO(PLUGIN_NAME ": %s:%d", __FUNCTION__, __LINE__);
++ char message[BUF_SIZE];
++
++ client = calloc(1, sizeof(struct client_info));
++ if (!client) {
++ ERROR("dpdk_telemetry: Memory could not be allocated\n");
++ return -1;
++ }
++ /*Here we look up the length of the g_dpdk_path string
++ * If it has a length we use it, otherwise we fall back to default
++ * See dpdk_telemetry_config() for details
++ */
++ client->dpdk_path = (strlen(g_dpdk_path)) ? g_dpdk_path : DEFAULT_DPDK_PATH;
++ client->client_path =
++ (strlen(g_client_path)) ? g_client_path : DEFAULT_CLIENT_PATH;
++ client->s_send = socket(AF_UNIX, SOCK_STREAM, 0);
++ if (client->s_send < 0) {
++ ERROR("dpdk_telemetry: Failed to open socket\n");
++ dpdk_telemetry_cleanup();
++ return -1;
++ }
++ client->s_recv = socket(AF_UNIX, SOCK_STREAM, 0);
++ if (client->s_recv < 0) {
++ ERROR("dpdk_telemetry: Failed to open message socket\n");
++ dpdk_telemetry_cleanup();
++ return -1;
++ }
++ client->addr.sun_family = AF_UNIX;
++ snprintf(client->addr.sun_path, sizeof(client->addr.sun_path), "%s",
++ client->dpdk_path);
++ if (connect(client->s_send, (struct sockaddr *)&client->addr,
++ sizeof(client->addr)) < 0) {
++ ERROR("dpdk_telemetry: Failed to connect\n");
++ dpdk_telemetry_cleanup();
++ return -1;
++ }
++ client->addrs.sun_family = AF_UNIX;
++ snprintf(client->addrs.sun_path, sizeof(client->addrs.sun_path), "%s",
++ client->client_path);
++ unlink(client->client_path);
++ if (bind(client->s_recv, (struct sockaddr *)&client->addrs,
++ sizeof(client->addrs)) < 0) {
++ ERROR("dpdk_telemetry: Failed to bind\n");
++ dpdk_telemetry_cleanup();
++ return -1;
++ }
++ if (listen(client->s_recv, 1) < 0) {
++ ERROR("dpdk_telemetry: Listen failed\n");
++ dpdk_telemetry_cleanup();
++ return -1;
++ }
++ snprintf(message, sizeof(message), "{\"action\":1,\"command\":\"clients\""
++ ",\"data\":{\"client_path\":\"%s\"}}",
++ client->client_path);
++ if (send(client->s_send, message, strlen(message), 0) < 0) {
++ ERROR("dpdk_telemetry: Could not send register message\n");
++ dpdk_telemetry_cleanup();
++ return -1;
++ }
++ client->fd = accept(client->s_recv, NULL, NULL);
++ if (client->fd < 0) {
++ ERROR("dpdk_telemetry: Failed to accept\n");
++ dpdk_telemetry_cleanup();
++ return -1;
++ }
++ user_data_t ud;
++ memset(&ud, 0, sizeof(ud));
++ ud.data = (void *)client;
++ plugin_register_complex_read(NULL, "dpdk_telemetry", dpdk_telemetry_read, 0,
++ &ud);
++ return 0;
++}
++
++static int dpdk_telemetry_shutdown(void) {
++ INFO(PLUGIN_NAME ": %s:%d", __FUNCTION__, __LINE__);
++ char msg[BUF_SIZE];
++ int ret;
++
++ snprintf(msg, sizeof(msg), "{\"action\":2,\"command\":\"clients\""
++ ",\"data\":{\"client_path\":\"%s\"}}",
++ client->client_path);
++ ret = send(client->fd, msg, strlen(msg), 0);
++ if (ret < 0) {
++ ERROR("dpdk_telemetry: Could not send unregister message\n");
++ dpdk_telemetry_cleanup();
++ return -1;
++ }
++ dpdk_telemetry_cleanup();
++ return 0;
++}
++
++void module_register(void) {
++ plugin_register_init("dpdk_telemetry", dpdk_telemetry_init);
++ plugin_register_complex_config("dpdk_telemetry", dpdk_telemetry_config);
++ plugin_register_shutdown("dpdk_telemetry", dpdk_telemetry_shutdown);
++}
+diff --git a/src/types.db b/src/types.db
+index f4933ee..4517b3c 100644
+--- a/src/types.db
++++ b/src/types.db
+@@ -76,6 +76,7 @@ dns_transfer value:DERIVE:0:U
+ dns_update value:DERIVE:0:U
+ dns_zops value:DERIVE:0:U
+ domain_state state:GAUGE:0:U, reason:GAUGE:0:U
++dpdk_telemetry value:DERIVE:0:U
+ drbd_resource value:DERIVE:0:U
+ duration seconds:GAUGE:0:U
+ email_check value:GAUGE:0:U
+--
+2.9.5
+