telemetry: introduce new functionality
[dpdk.git] / lib / librte_telemetry / telemetry.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(c) 2020 Intel Corporation
3  */
4
5 #include <unistd.h>
6 #include <pthread.h>
7 #include <sys/socket.h>
8 #include <sys/un.h>
9 #include <dlfcn.h>
10
11 /* we won't link against libbsd, so just always use DPDKs-specific strlcpy */
12 #undef RTE_USE_LIBBSD
13 #include <rte_string_fns.h>
14 #include <rte_common.h>
15 #include <rte_spinlock.h>
16 #include <rte_version.h>
17
18 #include "rte_telemetry.h"
19 #include "telemetry_json.h"
20 #include "telemetry_data.h"
21
22 #define MAX_CMD_LEN 56
23 #define MAX_HELP_LEN 64
24 #define MAX_OUTPUT_LEN (1024 * 16)
25
26 static void *
27 client_handler(void *socket);
28
29 struct cmd_callback {
30         char cmd[MAX_CMD_LEN];
31         telemetry_cb fn;
32         char help[MAX_HELP_LEN];
33 };
34
35 struct socket {
36         int sock;
37         char path[sizeof(((struct sockaddr_un *)0)->sun_path)];
38         handler fn;
39 };
40 static struct socket v2_socket; /* socket for v2 telemetry */
41 static char telemetry_log_error[1024]; /* Will contain error on init failure */
42 /* list of command callbacks, with one command registered by default */
43 static struct cmd_callback callbacks[TELEMETRY_MAX_CALLBACKS];
44 static int num_callbacks; /* How many commands are registered */
45 /* Used when accessing or modifying list of command callbacks */
46 static rte_spinlock_t callback_sl = RTE_SPINLOCK_INITIALIZER;
47
48 int
49 rte_telemetry_register_cmd(const char *cmd, telemetry_cb fn, const char *help)
50 {
51         int i = 0;
52
53         if (strlen(cmd) >= MAX_CMD_LEN || fn == NULL || cmd[0] != '/'
54                         || strlen(help) >= MAX_HELP_LEN)
55                 return -EINVAL;
56         if (num_callbacks >= TELEMETRY_MAX_CALLBACKS)
57                 return -ENOENT;
58
59         rte_spinlock_lock(&callback_sl);
60         while (i < num_callbacks && strcmp(cmd, callbacks[i].cmd) > 0)
61                 i++;
62         if (i != num_callbacks)
63                 /* Move elements to keep the list alphabetical */
64                 memmove(callbacks + i + 1, callbacks + i,
65                         sizeof(struct cmd_callback) * (num_callbacks - i));
66
67         strlcpy(callbacks[i].cmd, cmd, MAX_CMD_LEN);
68         callbacks[i].fn = fn;
69         strlcpy(callbacks[i].help, help, MAX_HELP_LEN);
70         num_callbacks++;
71         rte_spinlock_unlock(&callback_sl);
72
73         return 0;
74 }
75
76 static void
77 output_json(const char *cmd, const struct rte_tel_data *d, int s)
78 {
79         char out_buf[MAX_OUTPUT_LEN];
80
81         char *cb_data_buf;
82         size_t buf_len, prefix_used, used = 0;
83         unsigned int i;
84
85         RTE_BUILD_BUG_ON(sizeof(out_buf) < MAX_CMD_LEN +
86                         RTE_TEL_MAX_SINGLE_STRING_LEN + 10);
87         switch (d->type) {
88         case RTE_TEL_NULL:
89                 used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":null}",
90                                 MAX_CMD_LEN, cmd ? cmd : "none");
91                 break;
92         case RTE_TEL_STRING:
93                 used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":\"%.*s\"}",
94                                 MAX_CMD_LEN, cmd,
95                                 RTE_TEL_MAX_SINGLE_STRING_LEN, d->data.str);
96                 break;
97         case RTE_TEL_DICT:
98                 prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
99                                 MAX_CMD_LEN, cmd);
100                 cb_data_buf = &out_buf[prefix_used];
101                 buf_len = sizeof(out_buf) - prefix_used - 1; /* space for '}' */
102
103                 used = rte_tel_json_empty_obj(cb_data_buf, buf_len, 0);
104                 for (i = 0; i < d->data_len; i++) {
105                         const struct tel_dict_entry *v = &d->data.dict[i];
106                         switch (v->type) {
107                         case RTE_TEL_STRING_VAL:
108                                 used = rte_tel_json_add_obj_str(cb_data_buf,
109                                                 buf_len, used,
110                                                 v->name, v->value.sval);
111                                 break;
112                         case RTE_TEL_INT_VAL:
113                                 used = rte_tel_json_add_obj_int(cb_data_buf,
114                                                 buf_len, used,
115                                                 v->name, v->value.ival);
116                                 break;
117                         case RTE_TEL_U64_VAL:
118                                 used = rte_tel_json_add_obj_u64(cb_data_buf,
119                                                 buf_len, used,
120                                                 v->name, v->value.u64val);
121                                 break;
122                         }
123                 }
124                 used += prefix_used;
125                 used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
126                 break;
127         case RTE_TEL_ARRAY_STRING:
128         case RTE_TEL_ARRAY_INT:
129         case RTE_TEL_ARRAY_U64:
130                 prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
131                                 MAX_CMD_LEN, cmd);
132                 cb_data_buf = &out_buf[prefix_used];
133                 buf_len = sizeof(out_buf) - prefix_used - 1; /* space for '}' */
134
135                 used = rte_tel_json_empty_array(cb_data_buf, buf_len, 0);
136                 for (i = 0; i < d->data_len; i++)
137                         if (d->type == RTE_TEL_ARRAY_STRING)
138                                 used = rte_tel_json_add_array_string(
139                                                 cb_data_buf,
140                                                 buf_len, used,
141                                                 d->data.array[i].sval);
142                         else if (d->type == RTE_TEL_ARRAY_INT)
143                                 used = rte_tel_json_add_array_int(cb_data_buf,
144                                                 buf_len, used,
145                                                 d->data.array[i].ival);
146                         else if (d->type == RTE_TEL_ARRAY_U64)
147                                 used = rte_tel_json_add_array_u64(cb_data_buf,
148                                                 buf_len, used,
149                                                 d->data.array[i].u64val);
150                 used += prefix_used;
151                 used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
152                 break;
153         }
154         if (write(s, out_buf, used) < 0)
155                 perror("Error writing to socket");
156 }
157
158 static void
159 perform_command(telemetry_cb fn, const char *cmd, const char *param, int s)
160 {
161         struct rte_tel_data data;
162
163         int ret = fn(cmd, param, &data);
164         if (ret < 0) {
165                 char out_buf[MAX_CMD_LEN + 10];
166                 int used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":null}",
167                                 MAX_CMD_LEN, cmd ? cmd : "none");
168                 if (write(s, out_buf, used) < 0)
169                         perror("Error writing to socket");
170                 return;
171         }
172         output_json(cmd, &data, s);
173 }
174
175 static int
176 unknown_command(const char *cmd __rte_unused, const char *params __rte_unused,
177                 struct rte_tel_data *d)
178 {
179         return d->type = RTE_TEL_NULL;
180 }
181
182 static void *
183 client_handler(void *sock_id)
184 {
185         int s = (int)(uintptr_t)sock_id;
186         char buffer[1024];
187         char info_str[1024];
188         snprintf(info_str, sizeof(info_str),
189                         "{\"version\":\"%s\",\"pid\":%d,\"max_output_len\":%d}",
190                         rte_version(), getpid(), MAX_OUTPUT_LEN);
191         if (write(s, info_str, strlen(info_str)) < 0) {
192                 close(s);
193                 return NULL;
194         }
195
196         /* receive data is not null terminated */
197         int bytes = read(s, buffer, sizeof(buffer) - 1);
198         while (bytes > 0) {
199                 buffer[bytes] = 0;
200                 const char *cmd = strtok(buffer, ",");
201                 const char *param = strtok(NULL, ",");
202                 telemetry_cb fn = unknown_command;
203                 int i;
204
205                 if (cmd && strlen(cmd) < MAX_CMD_LEN) {
206                         rte_spinlock_lock(&callback_sl);
207                         for (i = 0; i < num_callbacks; i++)
208                                 if (strcmp(cmd, callbacks[i].cmd) == 0) {
209                                         fn = callbacks[i].fn;
210                                         break;
211                                 }
212                         rte_spinlock_unlock(&callback_sl);
213                 }
214                 perform_command(fn, cmd, param, s);
215
216                 bytes = read(s, buffer, sizeof(buffer) - 1);
217         }
218         close(s);
219         return NULL;
220 }
221
222 static void *
223 socket_listener(void *socket)
224 {
225         while (1) {
226                 pthread_t th;
227                 struct socket *s = (struct socket *)socket;
228                 int s_accepted = accept(s->sock, NULL, NULL);
229                 if (s_accepted < 0) {
230                         snprintf(telemetry_log_error,
231                                         sizeof(telemetry_log_error),
232                                         "Error with accept, telemetry thread quitting\n");
233                         return NULL;
234                 }
235                 pthread_create(&th, NULL, s->fn, (void *)(uintptr_t)s_accepted);
236                 pthread_detach(th);
237         }
238         return NULL;
239 }
240
241 static inline char *
242 get_socket_path(const char *runtime_dir, const int version)
243 {
244         static char path[PATH_MAX];
245         snprintf(path, sizeof(path), "%s/dpdk_telemetry.v%d",
246                         strlen(runtime_dir) ? runtime_dir : "/tmp", version);
247         return path;
248 }
249
250 static void
251 unlink_sockets(void)
252 {
253         if (v2_socket.path[0])
254                 unlink(v2_socket.path);
255 }
256
257 static int
258 create_socket(char *path)
259 {
260         int sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
261         if (sock < 0) {
262                 snprintf(telemetry_log_error, sizeof(telemetry_log_error),
263                                 "Error with socket creation, %s",
264                                 strerror(errno));
265                 return -1;
266         }
267
268         struct sockaddr_un sun = {.sun_family = AF_UNIX};
269         strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
270         unlink(sun.sun_path);
271         if (bind(sock, (void *) &sun, sizeof(sun)) < 0) {
272                 snprintf(telemetry_log_error, sizeof(telemetry_log_error),
273                                 "Error binding socket: %s",
274                                 strerror(errno));
275                 sun.sun_path[0] = 0;
276                 goto error;
277         }
278
279         if (listen(sock, 1) < 0) {
280                 snprintf(telemetry_log_error, sizeof(telemetry_log_error),
281                                 "Error calling listen for socket: %s",
282                                 strerror(errno));
283                 goto error;
284         }
285
286         return sock;
287
288 error:
289         close(sock);
290         unlink_sockets();
291         return -1;
292 }
293
294 static int
295 telemetry_v2_init(const char *runtime_dir)
296 {
297         pthread_t t_new;
298
299         v2_socket.fn = client_handler;
300         if (strlcpy(v2_socket.path, get_socket_path(runtime_dir, 2),
301                         sizeof(v2_socket.path)) >= sizeof(v2_socket.path)) {
302                 snprintf(telemetry_log_error, sizeof(telemetry_log_error),
303                                 "Error with socket binding, path too long");
304                 return -1;
305         }
306
307         v2_socket.sock = create_socket(v2_socket.path);
308         if (v2_socket.sock < 0)
309                 return -1;
310         pthread_create(&t_new, NULL, socket_listener, &v2_socket);
311         atexit(unlink_sockets);
312
313         return 0;
314 }
315
316 int32_t
317 rte_telemetry_new_init(void)
318 {
319         const char *error_str;
320         if (telemetry_v2_init(rte_eal_get_runtime_dir()) != 0) {
321                 error_str = telemetry_log_error;
322                 printf("Error initialising telemetry - %s", error_str);
323                 return -1;
324         }
325         return 0;
326 }