3614d6a412c4519db05786cf24f498c30e5bc6e1
[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 #include <rte_option.h>
18
19 #include "rte_telemetry.h"
20 #include "telemetry_json.h"
21 #include "telemetry_data.h"
22 #include "rte_telemetry_legacy.h"
23
24 #define MAX_CMD_LEN 56
25 #define MAX_HELP_LEN 64
26 #define MAX_OUTPUT_LEN (1024 * 16)
27
28 static void *
29 client_handler(void *socket);
30
31 struct cmd_callback {
32         char cmd[MAX_CMD_LEN];
33         telemetry_cb fn;
34         char help[MAX_HELP_LEN];
35 };
36
37 struct socket {
38         int sock;
39         char path[sizeof(((struct sockaddr_un *)0)->sun_path)];
40         handler fn;
41 };
42 static struct socket v2_socket; /* socket for v2 telemetry */
43 static struct socket v1_socket; /* socket for v1 telemetry */
44 static char telemetry_log_error[1024]; /* Will contain error on init failure */
45 /* list of command callbacks, with one command registered by default */
46 static struct cmd_callback callbacks[TELEMETRY_MAX_CALLBACKS];
47 static int num_callbacks; /* How many commands are registered */
48 /* Used when accessing or modifying list of command callbacks */
49 static rte_spinlock_t callback_sl = RTE_SPINLOCK_INITIALIZER;
50
51 int
52 rte_telemetry_register_cmd(const char *cmd, telemetry_cb fn, const char *help)
53 {
54         int i = 0;
55
56         if (strlen(cmd) >= MAX_CMD_LEN || fn == NULL || cmd[0] != '/'
57                         || strlen(help) >= MAX_HELP_LEN)
58                 return -EINVAL;
59         if (num_callbacks >= TELEMETRY_MAX_CALLBACKS)
60                 return -ENOENT;
61
62         rte_spinlock_lock(&callback_sl);
63         while (i < num_callbacks && strcmp(cmd, callbacks[i].cmd) > 0)
64                 i++;
65         if (i != num_callbacks)
66                 /* Move elements to keep the list alphabetical */
67                 memmove(callbacks + i + 1, callbacks + i,
68                         sizeof(struct cmd_callback) * (num_callbacks - i));
69
70         strlcpy(callbacks[i].cmd, cmd, MAX_CMD_LEN);
71         callbacks[i].fn = fn;
72         strlcpy(callbacks[i].help, help, MAX_HELP_LEN);
73         num_callbacks++;
74         rte_spinlock_unlock(&callback_sl);
75
76         return 0;
77 }
78
79 static int
80 list_commands(const char *cmd __rte_unused, const char *params __rte_unused,
81                 struct rte_tel_data *d)
82 {
83         int i;
84
85         rte_tel_data_start_array(d, RTE_TEL_STRING_VAL);
86         for (i = 0; i < num_callbacks; i++)
87                 rte_tel_data_add_array_string(d, callbacks[i].cmd);
88         return 0;
89 }
90
91 static int
92 json_info(const char *cmd __rte_unused, const char *params __rte_unused,
93                 struct rte_tel_data *d)
94 {
95         rte_tel_data_start_dict(d);
96         rte_tel_data_add_dict_string(d, "version", rte_version());
97         rte_tel_data_add_dict_int(d, "pid", getpid());
98         rte_tel_data_add_dict_int(d, "max_output_len", MAX_OUTPUT_LEN);
99         return 0;
100 }
101
102 static int
103 command_help(const char *cmd __rte_unused, const char *params,
104                 struct rte_tel_data *d)
105 {
106         int i;
107
108         if (!params)
109                 return -1;
110         rte_tel_data_start_dict(d);
111         rte_spinlock_lock(&callback_sl);
112         for (i = 0; i < num_callbacks; i++)
113                 if (strcmp(params, callbacks[i].cmd) == 0) {
114                         rte_tel_data_add_dict_string(d, params,
115                                         callbacks[i].help);
116                         break;
117                 }
118         rte_spinlock_unlock(&callback_sl);
119         if (i == num_callbacks)
120                 return -1;
121         return 0;
122 }
123
124 static void
125 output_json(const char *cmd, const struct rte_tel_data *d, int s)
126 {
127         char out_buf[MAX_OUTPUT_LEN];
128
129         char *cb_data_buf;
130         size_t buf_len, prefix_used, used = 0;
131         unsigned int i;
132
133         RTE_BUILD_BUG_ON(sizeof(out_buf) < MAX_CMD_LEN +
134                         RTE_TEL_MAX_SINGLE_STRING_LEN + 10);
135         switch (d->type) {
136         case RTE_TEL_NULL:
137                 used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":null}",
138                                 MAX_CMD_LEN, cmd ? cmd : "none");
139                 break;
140         case RTE_TEL_STRING:
141                 used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":\"%.*s\"}",
142                                 MAX_CMD_LEN, cmd,
143                                 RTE_TEL_MAX_SINGLE_STRING_LEN, d->data.str);
144                 break;
145         case RTE_TEL_DICT:
146                 prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
147                                 MAX_CMD_LEN, cmd);
148                 cb_data_buf = &out_buf[prefix_used];
149                 buf_len = sizeof(out_buf) - prefix_used - 1; /* space for '}' */
150
151                 used = rte_tel_json_empty_obj(cb_data_buf, buf_len, 0);
152                 for (i = 0; i < d->data_len; i++) {
153                         const struct tel_dict_entry *v = &d->data.dict[i];
154                         switch (v->type) {
155                         case RTE_TEL_STRING_VAL:
156                                 used = rte_tel_json_add_obj_str(cb_data_buf,
157                                                 buf_len, used,
158                                                 v->name, v->value.sval);
159                                 break;
160                         case RTE_TEL_INT_VAL:
161                                 used = rte_tel_json_add_obj_int(cb_data_buf,
162                                                 buf_len, used,
163                                                 v->name, v->value.ival);
164                                 break;
165                         case RTE_TEL_U64_VAL:
166                                 used = rte_tel_json_add_obj_u64(cb_data_buf,
167                                                 buf_len, used,
168                                                 v->name, v->value.u64val);
169                                 break;
170                         }
171                 }
172                 used += prefix_used;
173                 used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
174                 break;
175         case RTE_TEL_ARRAY_STRING:
176         case RTE_TEL_ARRAY_INT:
177         case RTE_TEL_ARRAY_U64:
178                 prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
179                                 MAX_CMD_LEN, cmd);
180                 cb_data_buf = &out_buf[prefix_used];
181                 buf_len = sizeof(out_buf) - prefix_used - 1; /* space for '}' */
182
183                 used = rte_tel_json_empty_array(cb_data_buf, buf_len, 0);
184                 for (i = 0; i < d->data_len; i++)
185                         if (d->type == RTE_TEL_ARRAY_STRING)
186                                 used = rte_tel_json_add_array_string(
187                                                 cb_data_buf,
188                                                 buf_len, used,
189                                                 d->data.array[i].sval);
190                         else if (d->type == RTE_TEL_ARRAY_INT)
191                                 used = rte_tel_json_add_array_int(cb_data_buf,
192                                                 buf_len, used,
193                                                 d->data.array[i].ival);
194                         else if (d->type == RTE_TEL_ARRAY_U64)
195                                 used = rte_tel_json_add_array_u64(cb_data_buf,
196                                                 buf_len, used,
197                                                 d->data.array[i].u64val);
198                 used += prefix_used;
199                 used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
200                 break;
201         }
202         if (write(s, out_buf, used) < 0)
203                 perror("Error writing to socket");
204 }
205
206 static void
207 perform_command(telemetry_cb fn, const char *cmd, const char *param, int s)
208 {
209         struct rte_tel_data data;
210
211         int ret = fn(cmd, param, &data);
212         if (ret < 0) {
213                 char out_buf[MAX_CMD_LEN + 10];
214                 int used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":null}",
215                                 MAX_CMD_LEN, cmd ? cmd : "none");
216                 if (write(s, out_buf, used) < 0)
217                         perror("Error writing to socket");
218                 return;
219         }
220         output_json(cmd, &data, s);
221 }
222
223 static int
224 unknown_command(const char *cmd __rte_unused, const char *params __rte_unused,
225                 struct rte_tel_data *d)
226 {
227         return d->type = RTE_TEL_NULL;
228 }
229
230 static void *
231 client_handler(void *sock_id)
232 {
233         int s = (int)(uintptr_t)sock_id;
234         char buffer[1024];
235         char info_str[1024];
236         snprintf(info_str, sizeof(info_str),
237                         "{\"version\":\"%s\",\"pid\":%d,\"max_output_len\":%d}",
238                         rte_version(), getpid(), MAX_OUTPUT_LEN);
239         if (write(s, info_str, strlen(info_str)) < 0) {
240                 close(s);
241                 return NULL;
242         }
243
244         /* receive data is not null terminated */
245         int bytes = read(s, buffer, sizeof(buffer) - 1);
246         while (bytes > 0) {
247                 buffer[bytes] = 0;
248                 const char *cmd = strtok(buffer, ",");
249                 const char *param = strtok(NULL, ",");
250                 telemetry_cb fn = unknown_command;
251                 int i;
252
253                 if (cmd && strlen(cmd) < MAX_CMD_LEN) {
254                         rte_spinlock_lock(&callback_sl);
255                         for (i = 0; i < num_callbacks; i++)
256                                 if (strcmp(cmd, callbacks[i].cmd) == 0) {
257                                         fn = callbacks[i].fn;
258                                         break;
259                                 }
260                         rte_spinlock_unlock(&callback_sl);
261                 }
262                 perform_command(fn, cmd, param, s);
263
264                 bytes = read(s, buffer, sizeof(buffer) - 1);
265         }
266         close(s);
267         return NULL;
268 }
269
270 static void *
271 socket_listener(void *socket)
272 {
273         while (1) {
274                 pthread_t th;
275                 struct socket *s = (struct socket *)socket;
276                 int s_accepted = accept(s->sock, NULL, NULL);
277                 if (s_accepted < 0) {
278                         snprintf(telemetry_log_error,
279                                         sizeof(telemetry_log_error),
280                                         "Error with accept, telemetry thread quitting\n");
281                         return NULL;
282                 }
283                 pthread_create(&th, NULL, s->fn, (void *)(uintptr_t)s_accepted);
284                 pthread_detach(th);
285         }
286         return NULL;
287 }
288
289 static inline char *
290 get_socket_path(const char *runtime_dir, const int version)
291 {
292         static char path[PATH_MAX];
293         snprintf(path, sizeof(path), "%s/dpdk_telemetry.v%d",
294                         strlen(runtime_dir) ? runtime_dir : "/tmp", version);
295         return path;
296 }
297
298 static void
299 unlink_sockets(void)
300 {
301         if (v2_socket.path[0])
302                 unlink(v2_socket.path);
303         if (v1_socket.path[0])
304                 unlink(v1_socket.path);
305 }
306
307 static int
308 create_socket(char *path)
309 {
310         int sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
311         if (sock < 0) {
312                 snprintf(telemetry_log_error, sizeof(telemetry_log_error),
313                                 "Error with socket creation, %s",
314                                 strerror(errno));
315                 return -1;
316         }
317
318         struct sockaddr_un sun = {.sun_family = AF_UNIX};
319         strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
320         unlink(sun.sun_path);
321         if (bind(sock, (void *) &sun, sizeof(sun)) < 0) {
322                 snprintf(telemetry_log_error, sizeof(telemetry_log_error),
323                                 "Error binding socket: %s",
324                                 strerror(errno));
325                 sun.sun_path[0] = 0;
326                 goto error;
327         }
328
329         if (listen(sock, 1) < 0) {
330                 snprintf(telemetry_log_error, sizeof(telemetry_log_error),
331                                 "Error calling listen for socket: %s",
332                                 strerror(errno));
333                 goto error;
334         }
335
336         return sock;
337
338 error:
339         close(sock);
340         unlink_sockets();
341         return -1;
342 }
343
344 static int
345 telemetry_legacy_init(const char *runtime_dir)
346 {
347         pthread_t t_old;
348
349         if (num_legacy_callbacks == 1) {
350                 snprintf(telemetry_log_error, sizeof(telemetry_log_error),
351                          "No legacy callbacks, legacy socket not created");
352                 return -1;
353         }
354
355         v1_socket.fn = legacy_client_handler;
356         if ((size_t) snprintf(v1_socket.path, sizeof(v1_socket.path),
357                         "%s/telemetry", runtime_dir)
358                         >= sizeof(v1_socket.path)) {
359                 snprintf(telemetry_log_error, sizeof(telemetry_log_error),
360                                 "Error with socket binding, path too long");
361                 return -1;
362         }
363         v1_socket.sock = create_socket(v1_socket.path);
364         if (v1_socket.sock < 0)
365                 return -1;
366         pthread_create(&t_old, NULL, socket_listener, &v1_socket);
367
368         return 0;
369 }
370
371 static int
372 telemetry_v2_init(const char *runtime_dir)
373 {
374         pthread_t t_new;
375
376         rte_telemetry_register_cmd("/", list_commands,
377                         "Returns list of available commands, Takes no parameters");
378         rte_telemetry_register_cmd("/info", json_info,
379                         "Returns DPDK Telemetry information. Takes no parameters");
380         rte_telemetry_register_cmd("/help", command_help,
381                         "Returns help text for a command. Parameters: string command");
382         v2_socket.fn = client_handler;
383         if (strlcpy(v2_socket.path, get_socket_path(runtime_dir, 2),
384                         sizeof(v2_socket.path)) >= sizeof(v2_socket.path)) {
385                 snprintf(telemetry_log_error, sizeof(telemetry_log_error),
386                                 "Error with socket binding, path too long");
387                 return -1;
388         }
389
390         v2_socket.sock = create_socket(v2_socket.path);
391         if (v2_socket.sock < 0)
392                 return -1;
393         pthread_create(&t_new, NULL, socket_listener, &v2_socket);
394         atexit(unlink_sockets);
395
396         return 0;
397 }
398
399 int32_t
400 rte_telemetry_init(void)
401 {
402         const char *error_str;
403         if (telemetry_v2_init(rte_eal_get_runtime_dir()) != 0) {
404                 error_str = telemetry_log_error;
405                 printf("Error initialising telemetry - %s", error_str);
406                 return -1;
407         }
408         if (telemetry_legacy_init(rte_eal_get_runtime_dir()) != 0) {
409                 error_str = telemetry_log_error;
410                 printf("No telemetry legacy support- %s", error_str);
411         }
412         return 0;
413 }
414
415 static struct rte_option option = {
416         .name = "telemetry",
417         .usage = "Enable telemetry backend",
418         .cb = &rte_telemetry_init,
419         .enabled = 0
420 };
421
422 RTE_INIT(telemetry_register_op) {
423         rte_option_register(&option);
424 }