From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from dpdk.org (dpdk.org [92.243.14.124]) by inbox.dpdk.org (Postfix) with ESMTP id 15848A0583; Thu, 19 Mar 2020 18:35:59 +0100 (CET) Received: from [92.243.14.124] (localhost [127.0.0.1]) by dpdk.org (Postfix) with ESMTP id B4A7A1C0AD; Thu, 19 Mar 2020 18:35:02 +0100 (CET) Received: from mga02.intel.com (mga02.intel.com [134.134.136.20]) by dpdk.org (Postfix) with ESMTP id 283541C07D for ; Thu, 19 Mar 2020 18:35:01 +0100 (CET) IronPort-SDR: JwoTLHjsh6+V1OseFk+WaohS0fUQ++C1b99awvyx+4nkowEust+XQkTWFGyFybJ+uR8M3GDUwE 1i1X30+/4+ug== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from orsmga004.jf.intel.com ([10.7.209.38]) by orsmga101.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 19 Mar 2020 10:35:00 -0700 IronPort-SDR: 2hHjiUkxc73fZ25jhuRVw2ta9W+mo/gQ70PKddWDbnasIR8CTMEfKDo3kCfKwA3WRVjppqIcgt JV2vd7X0TnFQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.70,572,1574150400"; d="scan'208";a="391872635" Received: from silpixa00399953.ir.intel.com (HELO silpixa00399953.ger.corp.intel.com) ([10.237.222.53]) by orsmga004.jf.intel.com with ESMTP; 19 Mar 2020 10:34:58 -0700 From: Ciara Power To: kevin.laatz@intel.com Cc: dev@dpdk.org, reshma.pattan@intel.com, Bruce Richardson , Ciara Power Date: Thu, 19 Mar 2020 17:18:59 +0000 Message-Id: <20200319171907.60891-5-ciara.power@intel.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200319171907.60891-1-ciara.power@intel.com> References: <20200319171907.60891-1-ciara.power@intel.com> Subject: [dpdk-dev] [PATCH 04/12] telemetry: introduce new telemetry functionality X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Sender: "dev" From: Bruce Richardson This patch introduces a new telemetry connection socket and handling functionality. Like the existing telemetry implementation (which is unaffected by this change) it uses a unix socket, but unlike the existing one it does not have a fixed list of commands - instead libraries or applications can register telemetry commands and callbacks to provide a full-extensible solution for all kinds of telemetry across DPDK. Signed-off-by: Bruce Richardson Signed-off-by: Ciara Power --- lib/librte_telemetry/Makefile | 4 + lib/librte_telemetry/meson.build | 6 +- lib/librte_telemetry/rte_telemetry.c | 3 + lib/librte_telemetry/rte_telemetry.h | 59 ++++ .../rte_telemetry_version.map | 2 + lib/librte_telemetry/telemetry.c | 257 ++++++++++++++++++ 6 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 lib/librte_telemetry/telemetry.c diff --git a/lib/librte_telemetry/Makefile b/lib/librte_telemetry/Makefile index 17e651c41..b34fe90d4 100644 --- a/lib/librte_telemetry/Makefile +++ b/lib/librte_telemetry/Makefile @@ -10,6 +10,9 @@ CFLAGS += -O3 CFLAGS += $(WERROR_FLAGS) -I$(SRCDIR) CFLAGS += -DALLOW_EXPERIMENTAL_API CFLAGS += -I$(RTE_SDK)/lib/librte_metrics/ +CFLAGS += -I$(RTE_SDK)/lib/librte_eal/common/include +CFLAGS += -I$(RTE_SDK)/lib/librte_eal/common/include/arch/$(ARCH_DIR)/ +CFLAGS += -pthread LDLIBS += -lrte_eal LDLIBS += -lrte_metrics @@ -22,6 +25,7 @@ EXPORT_MAP := rte_telemetry_version.map SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) := rte_telemetry.c SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += rte_telemetry_parser.c SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += rte_telemetry_parser_test.c +SRCS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += telemetry.c # export include files SYMLINK-$(CONFIG_RTE_LIBRTE_TELEMETRY)-include := rte_telemetry.h diff --git a/lib/librte_telemetry/meson.build b/lib/librte_telemetry/meson.build index b34661ac1..49c947120 100644 --- a/lib/librte_telemetry/meson.build +++ b/lib/librte_telemetry/meson.build @@ -1,7 +1,11 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2018 Intel Corporation -sources = files('rte_telemetry.c', 'rte_telemetry_parser.c', 'rte_telemetry_parser_test.c') +includes = [global_inc] +includes += include_directories('../librte_eal/common/include/arch/' + arch_subdir) + +sources = files('rte_telemetry.c', 'rte_telemetry_parser.c', 'rte_telemetry_parser_test.c', + 'telemetry.c') headers = files('rte_telemetry.h', 'rte_telemetry_internal.h', 'rte_telemetry_parser.h') cflags += '-DALLOW_EXPERIMENTAL_API' includes += include_directories('../librte_metrics') diff --git a/lib/librte_telemetry/rte_telemetry.c b/lib/librte_telemetry/rte_telemetry.c index 2fb8ffe87..45b6d9d94 100644 --- a/lib/librte_telemetry/rte_telemetry.c +++ b/lib/librte_telemetry/rte_telemetry.c @@ -503,6 +503,9 @@ rte_telemetry_init(void) return -EPERM; } + if (rte_telemetry_new_init() != 0) + return -1; + return 0; } diff --git a/lib/librte_telemetry/rte_telemetry.h b/lib/librte_telemetry/rte_telemetry.h index aedb31859..060613117 100644 --- a/lib/librte_telemetry/rte_telemetry.h +++ b/lib/librte_telemetry/rte_telemetry.h @@ -3,10 +3,13 @@ */ #include +#include #ifndef _RTE_TELEMETRY_H_ #define _RTE_TELEMETRY_H_ +#define TELEMETRY_MAX_CALLBACKS 64 + /** * @file * RTE Telemetry @@ -16,6 +19,33 @@ * a JSON encoded response containing telemetry data. ***/ +/** + * @warning + * @b EXPERIMENTAL: this API may change without prior notice + * + * This telemetry callback is used when registering a command. + * It handles getting and formatting stats to be returned to telemetry when + * requested. Stats up to buf_len in length are put in the buffer. + * + * @return + * Length of buffer used on success. + * @return + * Negative integer on error. + */ +typedef int (*telemetry_cb)(const char *cmd, const char *params, + char *buffer, int buf_len); + +/** + * @warning + * @b EXPERIMENTAL: this API may change without prior notice + * + * Used for handling data received over a telemetry socket. + * + * @return + * Void. + */ +typedef void * (*handler)(void *sock_id); + /** * @warning * @b EXPERIMENTAL: this API may change without prior notice @@ -66,4 +96,33 @@ __rte_experimental int32_t rte_telemetry_selftest(void); +/** + * @warning + * @b EXPERIMENTAL: this API may change without prior notice + * + * Used when registering a command and callback function with telemetry. + * + * @return + * 0 on success. + * @return + * -EINVAL for invalid parameters failure. + * @return + * -ENOENT if max callbacks limit has been reached. + */ +__rte_experimental +int rte_telemetry_register_cmd(const char *cmd, telemetry_cb fn); + +/** + * @warning + * @b EXPERIMENTAL: this API may change without prior notice + * + * Initialize new version of Telemetry. + * + * @return + * 0 on success. + * @return + * -1 on failure. + */ +__rte_experimental +int rte_telemetry_new_init(void); #endif diff --git a/lib/librte_telemetry/rte_telemetry_version.map b/lib/librte_telemetry/rte_telemetry_version.map index a80058c59..831bbd59a 100644 --- a/lib/librte_telemetry/rte_telemetry_version.map +++ b/lib/librte_telemetry/rte_telemetry_version.map @@ -6,6 +6,8 @@ EXPERIMENTAL { rte_telemetry_parse; rte_telemetry_selftest; rte_telemetry_set_metrics_fns; + rte_telemetry_new_init; + rte_telemetry_register_cmd; local: *; }; diff --git a/lib/librte_telemetry/telemetry.c b/lib/librte_telemetry/telemetry.c new file mode 100644 index 000000000..1ad784f59 --- /dev/null +++ b/lib/librte_telemetry/telemetry.c @@ -0,0 +1,257 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2019 Intel Corporation + */ + +#include +#include +#include +#include +#include + +/* we won't link against libbsd, so just always use DPDKs-specific strlcpy */ +#undef RTE_USE_LIBBSD +#include +#include +#include + +#include "rte_telemetry.h" + +#define MAX_CMD_LEN 56 + +static int +list_commands(const char *cmd __rte_unused, const char *params __rte_unused, + char *buffer, int buf_len); + +static void * +client_handler(void *socket); + +struct cmd_callback { + char cmd[MAX_CMD_LEN]; + telemetry_cb fn; +}; + +struct socket { + int sock; + char path[sizeof(((struct sockaddr_un *)0)->sun_path)]; + handler fn; +}; +static struct socket v2_socket; /* socket for v2 telemetry */ +static char telemetry_log_error[1024]; /* Will contain error on init failure */ +/* list of command callbacks, with one command registered by default */ +static struct cmd_callback callbacks[TELEMETRY_MAX_CALLBACKS] = { + { .cmd = "/", .fn = list_commands }, +}; +static int num_callbacks = 1; /* How many commands are registered */ +/* Used when accessing or modifying list of command callbacks */ +static rte_spinlock_t callback_sl = RTE_SPINLOCK_INITIALIZER; + +int +rte_telemetry_register_cmd(const char *cmd, telemetry_cb fn) +{ + int i = 0; + + if (strlen(cmd) >= MAX_CMD_LEN || fn == NULL || cmd[0] != '/') + return -EINVAL; + if (num_callbacks >= TELEMETRY_MAX_CALLBACKS) + return -ENOENT; + + rte_spinlock_lock(&callback_sl); + while (i < num_callbacks && strcmp(cmd, callbacks[i].cmd) > 0) + i++; + if (i != num_callbacks) + /* Move elements to keep the list alphabetical */ + memmove(callbacks + i + 1, callbacks + i, + sizeof(struct cmd_callback) * (num_callbacks - i)); + + strlcpy(callbacks[i].cmd, cmd, MAX_CMD_LEN); + callbacks[i].fn = fn; + num_callbacks++; + rte_spinlock_unlock(&callback_sl); + + return 0; +} + +static int +list_commands(const char *cmd __rte_unused, const char *params __rte_unused, + char *buffer, int buf_len) +{ + int i, ret, used = 0; + + used += strlcpy(buffer, "[", buf_len); + for (i = 0; i < num_callbacks; i++) { + ret = snprintf(buffer + used, buf_len - used, "\"%s\",", + callbacks[i].cmd); + if (ret + used >= buf_len) + break; + used += ret; + } + buffer[used - 1] = ']'; + return used; +} + +static void +perform_command(telemetry_cb fn, const char *cmd, const char *param, int s) +{ + char out_buf[1024 * 12]; + + int used = snprintf(out_buf, + sizeof(out_buf), "{\"%s\":", cmd); + int ret = fn(cmd, param, out_buf + used, sizeof(out_buf) - used); + if (ret < 0) { + used += strlcpy(out_buf + used, "null}\n", + sizeof(out_buf) - used); + if (write(s, out_buf, used) < 0) + perror("Error writing to socket"); + return; + } + used += ret; + used += strlcpy(out_buf + used, "}\n", sizeof(out_buf) - used); + if (write(s, out_buf, used) < 0) + perror("Error writing to socket"); +} + +static int +unknown_command(const char *cmd __rte_unused, const char *params __rte_unused, + char *buffer, int buf_len) +{ + return snprintf(buffer, buf_len, "null"); +} + +static void * +client_handler(void *sock_id) +{ + int s = (int)(uintptr_t)sock_id; + char buffer[1024]; + + /* receive data is not null terminated */ + int bytes = read(s, buffer, sizeof(buffer)); + buffer[bytes] = 0; + while (bytes > 0) { + const char *cmd = strtok(buffer, ","); + const char *param = strtok(NULL, ","); + telemetry_cb fn = unknown_command; + int i; + + rte_spinlock_lock(&callback_sl); + for (i = 0; i < num_callbacks; i++) + if (strcmp(cmd, callbacks[i].cmd) == 0) { + fn = callbacks[i].fn; + break; + } + + rte_spinlock_unlock(&callback_sl); + perform_command(fn, cmd, param, s); + + bytes = read(s, buffer, sizeof(buffer)); + buffer[bytes] = 0; + } + close(s); + return NULL; +} + +static void * +socket_listener(void *socket) +{ + while (1) { + pthread_t th; + struct socket *s = (struct socket *)socket; + int s_accepted = accept(s->sock, NULL, NULL); + if (s_accepted < 0) { + snprintf(telemetry_log_error, + sizeof(telemetry_log_error), + "Error with accept, telemetry thread quitting\n"); + return NULL; + } + pthread_create(&th, NULL, s->fn, (void *)(uintptr_t)s_accepted); + pthread_detach(th); + } + return NULL; +} + +static inline char * +get_socket_path(const char *runtime_dir) +{ + static char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/dpdk_telemetry.%d", + strlen(runtime_dir) ? runtime_dir : "/tmp", getpid()); + return path; +} + +static void +unlink_sockets(void) +{ + if (v2_socket.path[0]) + unlink(v2_socket.path); +} + +static int +create_socket(char *path) +{ + int sock = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (sock < 0) { + snprintf(telemetry_log_error, sizeof(telemetry_log_error), + "Error with socket creation, %s", + strerror(errno)); + return -1; + } + + struct sockaddr_un sun = {.sun_family = AF_UNIX}; + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + unlink(sun.sun_path); + if (bind(sock, (void *) &sun, sizeof(sun)) < 0) { + snprintf(telemetry_log_error, sizeof(telemetry_log_error), + "Error binding socket: %s", + strerror(errno)); + sun.sun_path[0] = 0; + goto error; + } + + if (listen(sock, 1) < 0) { + snprintf(telemetry_log_error, sizeof(telemetry_log_error), + "Error calling listen for socket: %s", + strerror(errno)); + goto error; + } + + return sock; + +error: + close(sock); + unlink_sockets(); + return -1; +} + +static int +telemetry_v2_init(const char *runtime_dir, const char **err_str) +{ + pthread_t t_new; + + v2_socket.fn = client_handler; + if (strlcpy(v2_socket.path, get_socket_path(runtime_dir), + sizeof(v2_socket.path)) >= sizeof(v2_socket.path)) { + snprintf(telemetry_log_error, sizeof(telemetry_log_error), + "Error with socket binding, path too long"); + return -1; + } + + v2_socket.sock = create_socket(v2_socket.path); + if (v2_socket.sock < 0) { + *err_str = telemetry_log_error; + return -1; + } + pthread_create(&t_new, NULL, socket_listener, &v2_socket); + atexit(unlink_sockets); + + return 0; +} + +int32_t +rte_telemetry_new_init(void) +{ + const char *error_str; + if (telemetry_v2_init(rte_eal_get_runtime_dir(), &error_str) != 0) { + printf("Error initialising telemetry - %s", error_str); + return -1; + } + return 0; +} -- 2.17.1